Stage 1

Pull in mouse cell metadata and retain eye cells.

Output the barcodes.

library(tidyverse)
sample_meta <- data.table::fread('~/git/scEiaD_quant/sample_meta.scEiaD_v1.2024_02_28.01.tsv.gz')
cell_meta <- data.table::fread('~/data/scEiaD_modeling/mm111.adata.solo.20240827.obs.csv.gz')[,-1] %>% 
  relocate(barcode) %>% 
  filter(solo_doublet == "FALSE")
sceiad_meta <- fst::read_fst('~/data/scEiaD_2022_02/meta_filter.fst')
cell_meta <- cell_meta %>% 
  left_join(sceiad_meta %>% select(barcode = Barcode, CellType_predict), by = 'barcode')
mm111_eye <- cell_meta %>% 
  filter(
    organ == 'Eye',
    organism == 'Mus musculus',
    !grepl("^#", sample_accession),
    source == 'Tissue')

#mm111_eye$barcode %>% write(gzfile('~/git/scEiaD_modeling/data/mm111_eye_bcs.20250115.csv.gz'))

Stage 2

Use existing models to predict cell types:

  1. Chen HRCA
  2. Chen MRCA
  3. Sanes “Complete Ocular Atlas” (which I will dub COA)
  4. scEiaD neural 20250107
  5. scEiaD non-neural 20250107
  6. scEiaD full ocular 20250107
  7. scEiaD full ocular 20250107 “mmGeneFilter” (genes names available for HVG filtered down to ones that match across HCOP mouse/human)
# rsync the csv to biowulf2 (not shown)
cd /data/OGVFB_BG/scEiaD/2024_02_28/snakeout/mm111_mature_eye_full
sinteractive --mem=128G --time=8:00:00  --gres=gpu:a100:1
source /data/$USER/conda/etc/profile.d/conda.sh && source /data/$USER/conda/etc/profile.d/mamba.sh
mamba activate rapids_singlecell
bash ct_projection_call.sh
cd /Users/mcgaugheyd/data/scEiaD_modeling/mm111_mature_eye_full
rsync -Prav h2:/data/OGVFB_BG/scEiaD/2024_02_28/snakeout/mm111_mature_eye_full/*CT_projections_20120114* .

Chen MRCA

ctp_mm111__mrca <- data.table::fread('/Users/mcgaugheyd/data/scEiaD_modeling/mm111_mature_eye_full/mm111.eye.mouse_CT_projections_20120115.csv.gz') %>% select(-17) %>% left_join(sceiad_meta %>% select(barcode = Barcode, CellType_predict), by = 'barcode') %>% 
  # only look at >= 10 day cells
  mutate(age = as.numeric(age)) %>% 
  filter(age >= 10 | is.na(age))
Warning: There was 1 warning in `mutate()`.
ℹ In argument: `age = as.numeric(age)`.
Caused by warning:
! NAs introduced by coercion
label <- 'MajorCellType'
machine_label <- 'CT__chen_mrca'
most_common_mislabel <- ctp_mm111__mrca %>% 
  group_by(.data[[label]],.data[[machine_label]]) %>% 
  summarise(Count = n()) %>% 
  arrange(.data[[label]], -Count) %>% 
  slice_max(order_by = Count, n = 2) %>% 
  filter(.data[[label]] != .data[[machine_label]])
`summarise()` has grouped output by 'MajorCellType'. You can override using the `.groups` argument.
ctp_mm111__mrca %>% 
  mutate(tf = case_when(.data[[label]] == .data[[machine_label]] ~ TRUE,
                        TRUE ~ FALSE)) %>% 
  group_by(.data[[label]]) %>% 
  summarise(accuracy = sum(tf)/length(tf)) %>% 
  arrange(.data[[label]]) %>% 
  left_join(most_common_mislabel %>% 
              select(any_of(label), 
                     `most common mislabel` = any_of(machine_label),
                     `mislabel count` = Count))
Joining with `by = join_by(MajorCellType)`

UMAP

umap1 = 'umap1_chen_mrca'
umap2 = 'umap2_chen_mrca'
ct = 'CT__chen_mrca'
color = 'CT__chen_mrca'
score <- 'CT__chen_mrca__max_score'
umap_ct_plotter <- function(obj, umap1, umap2, ct, color, labels = TRUE){
  plot <- obj %>% 
    mutate(leiden = as.factor(leiden)) %>% 
    ggplot(aes(x=.data[[umap1]],y=.data[[umap2]])) +
    scattermore::geom_scattermore(aes(color = .data[[color]]), pointsize = 0.8, alpha = 0.5) +
    scale_color_manual(values = c(pals::alphabet2(), pals::glasbey(), pals::kelly(), pals::alphabet()) %>%
                         unname()) + 
    cowplot::theme_cowplot() + theme(legend.position = "none")
  if (labels){
    plot <- plot + ggrepel::geom_label_repel(data = . %>% 
                                               group_by(.data[[ct]]) %>% 
                                               summarise(across(all_of(c(umap1,umap2)), median)),
                                             aes(label = .data[[ct]], color = .data[[color]])) 
    
  } 
  print(plot)
}

umap_score_plotter <- function(obj, umap1, umap2, ct, score){
  obj %>% 
    ggplot(aes(x=.data[[umap1]],y=.data[[umap2]])) +
    scattermore::geom_scattermore(aes(color = .data[[score]]), pointsize = 0.8, alpha = 0.5) +
    ggrepel::geom_label_repel(data = . %>% 
                                group_by(.data[[ct]]) %>% 
                                summarise(across(all_of(c(umap1,umap2)), median)),
                              aes(label = .data[[ct]])) + 
    scale_color_viridis_c() +
    cowplot::theme_cowplot() + theme(legend.position = "none")
}
umap_ct_plotter(ctp_mm111__mrca, umap1, umap2, ct, color)

umap_ct_plotter(ctp_mm111__mrca, umap1, umap2, 'leiden', 'leiden')

umap_score_plotter(ctp_mm111__mrca, umap1, umap2, ct, score)

umap1 = 'umap1_chen_mrca'
umap2 = 'umap2_chen_mrca'
ct = 'CellType_predict'
color = 'CellType_predict'
umap_ct_plotter(ctp_mm111__mrca, umap1, umap2, ct, color)

Proportion of CT calls across each leiden cluster

CellType predict from scEiaD 2022 (web / published)

ct <- 'CellType_predict'

bar_plotter <- function(data = ctp_mm111, a = 'leiden', b = ct){
  data %>% 
    mutate(leiden = as.factor(leiden)) %>% 
    group_by(.data[[a]], .data[[b]]) %>% 
    summarise(Count = n()) %>% 
    mutate(Ratio = Count / sum(Count),
           across(all_of(b), stringr::str_wrap,width = 15)) %>% 
    filter(Ratio > 0.05) %>% 
    ggplot(aes(x=Ratio,y=.data[[a]], fill = 
                 .data[[b]], label = 
                 .data[[b]])) +
    geom_bar(stat='identity') + 
    #shadowtext::geom_shadowtext(position = position_stack(vjust = 0.5), color = 'black', bg.colour='white') +
    ggrepel::geom_text_repel(position = position_stack(vjust = 0.5), bg.color = 'white', direction = 'x')  +
    scale_fill_manual(values = c(pals::alphabet2(), pals::glasbey()) %>% unname()) + 
    cowplot::theme_cowplot() +
    #scale_x_continuous(limits = c(0,1.1)) +
    coord_cartesian(clip = "off") 
}
bar_plotter(ctp_mm111__mrca, b = ct)
`summarise()` has grouped output by 'leiden'. You can override using the `.groups` argument.

CellType predict from Chen MRCA

ct <- 'CT__chen_mrca'
bar_plotter(ctp_mm111__mrca, b = ct)
`summarise()` has grouped output by 'leiden'. You can override using the `.groups` argument.

scEiaD Human Mature Eye Full 20250107

ctp_mm111_sceiad <- data.table::fread('/Users/mcgaugheyd/data/scEiaD_modeling/mm111_mature_eye_full/mm111.eye.human_CT_projections_20120115.csv.gz') %>% select(-17) %>% left_join(sceiad_meta %>% select(barcode = Barcode, CellType_predict), by = 'barcode')%>% 
  # only look at >= 10 day cells
  mutate(age = as.numeric(age)) %>% 
  filter(age >= 10 | is.na(age))
Warning: There was 1 warning in `mutate()`.
ℹ In argument: `age = as.numeric(age)`.
Caused by warning:
! NAs introduced by coercion
label <- 'CellType_predict'
machine_label <- 'CT__sceiad_20250107_full_mmGeneFilter'
most_common_mislabel <- ctp_mm111_sceiad %>% 
  group_by(.data[[label]],.data[[machine_label]]) %>% 
  summarise(Count = n()) %>% 
  arrange(.data[[label]], -Count) %>% 
  slice_max(order_by = Count, n = 2) %>% 
  filter(.data[[label]] != .data[[machine_label]])
`summarise()` has grouped output by 'CellType_predict'. You can override using the `.groups` argument.
ctp_mm111_sceiad %>% 
  mutate(tf = case_when(.data[[label]] == .data[[machine_label]] ~ TRUE,
                        TRUE ~ FALSE)) %>% 
  group_by(.data[[label]]) %>% 
  summarise(accuracy = sum(tf)/length(tf)) %>% 
  arrange(.data[[label]]) %>% 
  left_join(most_common_mislabel %>% 
              select(any_of(label), 
                     `most common mislabel` = any_of(machine_label),
                     `mislabel count` = Count))
Joining with `by = join_by(CellType_predict)`

UMAP

umap1 = 'umap1_sceiad_20250107_full_mmGeneFilter'
umap2 = 'umap2_sceiad_20250107_full_mmGeneFilter'
ct = 'CT__sceiad_20250107_full_mmGeneFilter'
color = 'CT__sceiad_20250107_full_mmGeneFilter'
score <- 'CT__sceiad_20250107_full_mmGeneFilter__max_score'


umap_ct_plotter(ctp_mm111_sceiad, umap1, umap2, ct, color)

umap_ct_plotter(ctp_mm111_sceiad, umap1, umap2, 'leiden', 'leiden')

umap_score_plotter(ctp_mm111_sceiad, umap1, umap2, ct, score)

umap1 = 'umap1_sceiad_20250107_full_mmGeneFilter'
umap2 = 'umap2_sceiad_20250107_full_mmGeneFilter'
ct = 'CellType_predict'
color = 'CellType_predict'
umap_ct_plotter(ctp_mm111_sceiad, umap1, umap2, ct, color)

Proportion of CT calls across each leiden cluster

CellType predict from scEiaD 2022 (web / published)

ct <- 'CellType_predict'

bar_plotter <- function(data = ctp_mm111, a = 'leiden', b = ct){
  data %>% 
    mutate(leiden = as.factor(leiden)) %>% 
    group_by(.data[[a]], .data[[b]]) %>% 
    summarise(Count = n()) %>% 
    mutate(Ratio = Count / sum(Count),
           across(all_of(b), stringr::str_wrap,width = 15)) %>% 
    filter(Ratio > 0.05) %>% 
    ggplot(aes(x=Ratio,y=.data[[a]], fill = 
                 .data[[b]], label = 
                 .data[[b]])) +
    geom_bar(stat='identity') + 
    #shadowtext::geom_shadowtext(position = position_stack(vjust = 0.5), color = 'black', bg.colour='white') +
    ggrepel::geom_text_repel(position = position_stack(vjust = 0.5), bg.color = 'white', direction = 'x')  +
    scale_fill_manual(values = c(pals::alphabet2(), pals::glasbey()) %>% unname()) + 
    cowplot::theme_cowplot() +
    scale_x_continuous(limits = c(0,1.1)) +
    coord_cartesian(clip = "off") 
}
bar_plotter(ctp_mm111_sceiad, 'leiden', ct)
`summarise()` has grouped output by 'leiden'. You can override using the `.groups` argument.

CellType predict from scEiaD 2025 Human Full Eye

ct <- 'CT__sceiad_20250107_full_mmGeneFilter'
bar_plotter(ctp_mm111_sceiad, 'leiden', ct)
`summarise()` has grouped output by 'leiden'. You can override using the `.groups` argument.

Alignment of Cell Type Calls

Using: 1. MRCA Predictions 2. scEiaD Human Eye (mmGeneFilter) Predictions 3. MajorCellType (curated author) 4. CellType_predict (scEiaD 2022 publication machine labels)

bind_cols(
  ctp_mm111__mrca,
  ctp_mm111_sceiad %>% select(contains('geneFilter'))
) %>% 
  group_by(CT__sceiad_20250107_full_mmGeneFilter, MajorCellType, CellType_predict, CT__chen_mrca) %>% 
  mutate(CT__sceiad_20250107_full_mmGeneFilter = gsub("\\s\\(.*|\\srod","",CT__sceiad_20250107_full_mmGeneFilter),
         CellType_predict = tolower(CellType_predict) %>% gsub("\\scell|\\sglia|\\srod","",.),
         CT__chen_mrca = case_when(grepl("amacrine", CT__chen_mrca) ~ "amacrine",
                                   grepl("retinal cone", CT__chen_mrca) ~ "cone",
                                   grepl("retinal rod", CT__chen_mrca) ~ "rod",
                                   grepl("bipolar", CT__chen_mrca) ~ "bipolar",
                                   grepl("horizontal", CT__chen_mrca) ~ "horizontal",
                                   grepl("retinal pigment", CT__chen_mrca) ~ "rpe",
                                   TRUE ~ CT__chen_mrca) %>% tolower() %>% gsub(" cell","",.)) %>% 
  summarise(Count = n()) %>% 
  arrange(CT__sceiad_20250107_full_mmGeneFilter, -Count) %>% 
  filter(Count > 5) %>% 
  DT::datatable() 
`summarise()` has grouped output by 'CT__sceiad_20250107_full_mmGeneFilter', 'MajorCellType', 'CellType_predict'. You can override using the `.groups` argument.
ct_call_counts <- bind_cols(
  ctp_mm111__mrca,
  ctp_mm111_sceiad %>% select(contains('geneFilter'))
) %>% 
  select(barcode, CT__sceiad_20250107_full_mmGeneFilter, MajorCellType, CellType_predict, CT__chen_mrca) %>% 
  mutate(CT__sceiad_20250107_full_mmGeneFilter = gsub("\\s\\(.*|\\srod","",CT__sceiad_20250107_full_mmGeneFilter),
         CellType_predict = tolower(CellType_predict) %>% gsub("\\scell|\\sglia|\\srod","",.),
         CT__chen_mrca = case_when(grepl("amacrine", CT__chen_mrca) ~ "amacrine",
                                   grepl("retinal cone", CT__chen_mrca) ~ "cone",
                                   grepl("retinal rod", CT__chen_mrca) ~ "rod",
                                   grepl("bipolar", CT__chen_mrca) ~ "bipolar",
                                   grepl("horizontal", CT__chen_mrca) ~ "horizontal",
                                   grepl("retinal pigment", CT__chen_mrca) ~ "rpe",
                                   TRUE ~ CT__chen_mrca) %>% tolower() %>% gsub(" cell","",.)) %>% 
  pivot_longer(-barcode) %>% 
  mutate(value = case_when(is.na(value) ~ '',
                           TRUE ~ value)) %>% 
  group_by(barcode, value) %>% 
  summarise(Count = n())
`summarise()` has grouped output by 'barcode'. You can override using the `.groups` argument.
consensus <- ct_call_counts %>% 
  filter(value != '', Count > 1) %>% 
  slice_max(order_by = Count, n = 1)

Output Barcodes

set.seed(20250154)
mm111_full_eye_ref_bcs <- ctp_mm111__mrca %>% ungroup() %>% 
  left_join(consensus %>% select(barcode, consensus = value, consensus_count = Count)) %>% 
  mutate(consensus = case_when(is.na(consensus) ~ 'Unknown', TRUE ~ consensus)) %>% 
  group_by(study_accession, consensus) %>% 
  sample_n(500, replace = TRUE) %>% 
  unique()
Joining with `by = join_by(barcode)`
mm111_full_eye_query_bcs <- ctp_mm111__mrca %>% ungroup() %>% 
  filter(!barcode %in% mm111_full_eye_ref_bcs$barcode)

#mm111_full_eye_ref_bcs$barcode %>% write(gzfile('~/git/scEiaD_modeling/data/mm111_mature_eye_ref_bcs.full.20250115.csv.gz'))

#mm111_full_eye_query_bcs$barcode %>% write(gzfile('~/git/scEiaD_modeling/data/mm111_mature_eye_query_bcs.full.20250115.csv.gz'))

Stage 3

Run scVI ~/git/scEiaD_modeling/Snakemake.wrapper.sh pipeline with the ref / query barcodes

cd /data/OGVFB_BG/scEiaD/2024_02_28/snakeout/mm111_mature_eye_full
sbatch snakecall.sh
rsync -Prav h2:/data/OGVFB_BG/scEiaD/2024_02_28/snakeout/mm111_mature_eye_full/mm111_mature_eye_20250113_2000hvg_100e_20l.obs.csv.gz /Users/mcgaugheyd/data/scEiaD_modeling/mm111_mature_eye_full/
source('analysis_scripts.R')

mm_obs <- pull_obs('~/data/scEiaD_modeling/mm111_mature_eye_full/mm111_mature_eye_20250114_2000hvg_100e_20l.obs.csv.gz', machine_label = 'MCT_scANVI') 
`summarise()` has grouped output by 'leiden3'. You can override using the `.groups` argument.`summarise()` has grouped output by 'leiden3'. You can override using the `.groups` argument.`summarise()` has grouped output by 'leiden3'. You can override using the `.groups` argument.`summarise()` has grouped output by 'leiden3'. You can override using the `.groups` argument.`summarise()` has grouped output by 'leiden3'. You can override using the `.groups` argument.`summarise()` has grouped output by 'leiden3'. You can override using the `.groups` argument.`summarise()` has grouped output by 'leiden3'. You can override using the `.groups` argument.
mm_obs$obs <- mm_obs$obs %>% 
  dplyr::rename(barcode =barcodei) %>% 
  left_join(sceiad_meta %>% select(barcode = Barcode, CellType_predict), by = 'barcode')

UMAP Plots

a <- mm_obs$obs %>% 
  left_join(consensus %>% select(barcode, consensus = value, consensus_count = Count)) %>% 
  group_by(consensus) %>% 
  sample_n(5000, replace = TRUE) %>% 
  unique() %>% 
  ggplot(aes(x=umap1,y=umap2)) +
  scattermore::geom_scattermore(aes(color = consensus), pointsize = 0.8, alpha = 0.5) +
  ggrepel::geom_label_repel(data = . %>% group_by(consensus) %>% 
                              summarise(umap1 = median(umap1),
                                        umap2 = median(umap2)),
                            aes(label = consensus, color = consensus)) +
  scale_color_manual(values = c(pals::alphabet2(), pals::glasbey()) %>% unname()) + 
  cowplot::theme_cowplot() + theme(legend.position = "none") +
  ggtitle("consensus")
Joining with `by = join_by(barcode)`Warning: Detected an unexpected many-to-many relationship between `x` and `y`.
b <- mm_obs$obs %>% 
  left_join(mm_obs$labels, by = 'leiden3') %>% 
  ggplot(aes(x=umap1,y=umap2)) +
  scattermore::geom_scattermore(aes(color = MajorCellType), pointsize = 0.8, alpha = 0.5) +
  ggrepel::geom_label_repel(data = . %>% group_by(MajorCellType) %>% 
                              summarise(umap1 = median(umap1),
                                        umap2 = median(umap2)),
                            aes(label = MajorCellType, color = MajorCellType)) +
  scale_color_manual(values = c(pals::alphabet2(), pals::glasbey()) %>% unname()) + 
  cowplot::theme_cowplot() + theme(legend.position = "none") +
  ggtitle("MajorCellType")

cowplot::plot_grid(a,b,nrow = 1)
Warning: Removed 1 row containing missing values or values outside the scale range (`geom_label_repel()`).


c <- mm_obs$obs %>% 
  left_join(consensus %>% select(barcode, consensus = value, consensus_count = Count)) %>% 
  group_by(consensus) %>% 
  sample_n(5000, replace = TRUE) %>% 
  unique() %>% 
  ggplot(aes(x=umap1,y=umap2)) +
  scattermore::geom_scattermore(aes(color = consensus), pointsize = 4.8, alpha = 0.5) +
  facet_wrap(~study_accession) +
  scale_color_manual(values = c(pals::alphabet2(), pals::glasbey()) %>% unname()) + 
  cowplot::theme_cowplot() + theme(legend.position = "none") +
  ggtitle("color by consensus, split by study")
Joining with `by = join_by(barcode)`Warning: Detected an unexpected many-to-many relationship between `x` and `y`.
d <- mm_obs$obs %>% 
  left_join(consensus %>% select(barcode, consensus = value, consensus_count = Count)) %>% 
  group_by(consensus) %>% 
  sample_n(5000, replace = TRUE) %>% 
  unique() %>% 
  ggplot(aes(x=umap1,y=umap2)) +
  scattermore::geom_scattermore(aes(color = study_accession),pointsize = 4.8, alpha = 1) +
  facet_wrap(~consensus) +
  scale_color_manual(values = c(pals::alphabet2(), pals::glasbey()) %>% unname()) + 
  cowplot::theme_cowplot() +
  ggtitle("color by study, split by consensus")
Joining with `by = join_by(barcode)`Warning: Detected an unexpected many-to-many relationship between `x` and `y`.
cowplot::plot_grid(c,d,nrow=1)

Looks Good! Now let do some trimming according to leiden clustering

pseudobulk (leiden3) tree

Goal: remove wacky looking clusters

library(ggtree)
pb <- data.table::fread('~/data/scEiaD_modeling/mm111_mature_eye_full/mm111_mature_eye_20250114_2000hvg_100e_20l.pseudoBulk.leiden3.csv.gz')
colnames(pb) <- gsub("\\.\\d+","",colnames(pb))
hvg <- data.table::fread('~/data/scEiaD_modeling/mm111_mature_eye_full/hvg.2000.csv.gz')[-1,]
rnames <- pb$V1
clust <- str_extract(rnames, '\\d+') %>% as.integer()
pb <- pb[,-1] %>% as.matrix()
row.names(pb) <- as.character(clust)

pb_norm <- metamoRph::normalize_data(t(pb), sample_scale = 'cpm') %>% t() 
Sample CPM scaling
log1p scaling
pb_norm <- pb_norm[,hvg$V2]
#pb_norm <- pb_norm[,hvg$V2[!hvg$V2 %in% c(cc_genes,ribo_genes)]]
# https://stats.stackexchange.com/questions/31565/compute-a-cosine-dissimilarity-matrix-in-r
sim <- pb_norm / sqrt(rowSums(pb_norm * pb_norm))
sim <- sim %*% t(sim)
D_sim <- as.dist(1 - sim)

hclust_sim <- hclust(D_sim, method = 'average')

pb_labels <- mm_obs$labels %>% mutate(leiden3=as.character(leiden3))

# machine calls
p <- ggtree(hclust_sim)
p$data <- p$data %>% left_join(pb_labels, by = c("label" = "leiden3")) 
f <- p + layout_dendrogram() +
  geom_tiplab(aes(label = paste(label, mMCT, studyCount, TotalCount, sep = ' - '), color = mCT)) + 
  theme_dendrogram(plot.margin=margin(16,16,300,16)) +
  scale_color_manual(values = c(pals::alphabet2(), pals::glasbey()) %>% unname()) + 
  guides(color="none")

# consensus calls
pb_labels <- mm_obs$obs %>% 
  left_join(consensus %>% select(barcode, consensus = value, consensus_count = Count), by = 'barcode') %>% 
  group_by(leiden3, consensus) %>% 
  summarise(Count = n()) %>% 
  mutate(Ratio = Count / sum(Count)) %>% 
  filter(Ratio > 0.1) %>% 
  arrange(-Ratio) %>% 
  mutate(cperc = paste0(consensus, " (", round(Ratio,2), ")")) %>% 
  summarise(CTs = paste0(cperc, collapse = ', ')) %>% 
  mutate(leiden3 = as.character(leiden3),
         CTm = gsub(" \\(.*","",CTs))
Warning: Detected an unexpected many-to-many relationship between `x` and `y`.`summarise()` has grouped output by 'leiden3'. You can override using the `.groups` argument.
p <- ggtree(hclust_sim)
p$data <- p$data %>% left_join(pb_labels, by = c("label" = "leiden3")) 
g <- p + layout_dendrogram() +
  geom_tiplab(aes(label = paste(label,CTs, sep = ' - '), color = CTm)) + 
  theme_dendrogram(plot.margin=margin(16,16,300,16)) +
  scale_color_manual(values = c(pals::alphabet2(), pals::glasbey()) %>% unname()) + 
  guides(color="none")

h <- mm_obs$obs %>% mutate(leiden3= as.factor(leiden3)) %>% 
  ggplot(aes(x=umap1,y=umap2)) +
  scattermore::geom_scattermore(aes(color = leiden3), pointsize = 0.8, alpha = 0.5) +
  ggrepel::geom_text_repel(data = . %>% group_by(leiden3) %>% 
                             summarise(umap1 = median(umap1),
                                       umap2 = median(umap2)),
                           aes(label = leiden3, color = leiden3), bg.color = "white") +
  scale_color_manual(values = c(pals::alphabet2(), pals::glasbey(), pals::kelly(), pals::alphabet(), pals::brewer.set1(n=10)) %>% unname()) + 
  cowplot::theme_cowplot() + theme(legend.position = "none") +
  ggtitle("consensus")

cowplot::plot_grid(cowplot::plot_grid(f,g, nrow =2),
                   cowplot::plot_grid(a,h, nrow = 1),
                   nrow = 2, rel_heights = c(1,0.4))
Warning: Removed 1 row containing missing values or values outside the scale range (`geom_label_repel()`).

remove_leiden3 <- c(105,
                    97,
                    101,
                    71,
                    16,
                    6,
                    87,
                    99,
                    73, 39, 60, 82)

mm_obs$obs %>% 
  left_join(consensus %>% select(barcode, consensus = value, consensus_count = Count)) %>% 
  group_by(consensus) %>% 
  sample_n(5000, replace = TRUE) %>% 
  unique() %>% 
  mutate(remove = case_when(leiden3 %in% remove_leiden3 ~ 'remove',
                            TRUE ~ 'retain')) %>% 
  ggplot(aes(x=umap1,y=umap2)) +
  scattermore::geom_scattermore(aes(color = consensus), pointsize = 3.8, alpha = 0.5) +
  scale_color_manual(values = c(pals::alphabet2(), pals::glasbey()) %>% unname()) + 
  cowplot::theme_cowplot() + theme(legend.position = "none") +
  ggtitle("consensus") +
  facet_wrap(~remove)
Joining with `by = join_by(barcode)`Warning: Detected an unexpected many-to-many relationship between `x` and `y`.

Easy and Medium Calls

Easy is any cluster with >90% consensus

Medium is any cluster that I can easily hand verify as >90% (often you get combos that are effectively the same).

easy_calls <- mm_obs$obs %>% 
  filter(!leiden3 %in% remove_leiden3) %>% 
  left_join(consensus %>% select(barcode, consensus = value, consensus_count = Count), by = 'barcode') %>% 
  group_by(leiden3, consensus) %>% 
  summarise(Count = n()) %>% 
  mutate(Ratio = Count / sum(Count)) %>% 
  filter(Ratio > 0.9, !is.na(consensus))
Warning: Detected an unexpected many-to-many relationship between `x` and `y`.`summarise()` has grouped output by 'leiden3'. You can override using the `.groups` argument.
remainder_01 <- mm_obs$obs %>% filter(!leiden3 %in% easy_calls$leiden3) %>% 
  filter(!leiden3 %in% remove_leiden3) %>% 
  left_join(ctp_mm111__mrca %>% select(barcode,CT__chen_mrca), by = 'barcode') %>% 
  left_join(ctp_mm111_sceiad %>% select(barcode,CT__sceiad_20250107_full_mmGeneFilter, CT__sanes_complete_ocular_atlas_SCP2310), by = 'barcode') 

remainder_01 %>% 
  group_by(leiden3, MajorCellType, CT__sceiad_20250107_full_mmGeneFilter, CT__chen_mrca) %>% 
  summarise(Count =n()) %>% 
  left_join(remainder_01 %>% group_by(leiden3) %>% summarise(lCount = n()), by ='leiden3') %>% 
  mutate(Ratio = Count/lCount) %>% filter(Ratio > 0.05) %>% 
  DT::datatable()
`summarise()` has grouped output by 'leiden3', 'MajorCellType', 'CT__sceiad_20250107_full_mmGeneFilter'. You can override using the `.groups` argument.
cts <- list()
cts$cone <- c(18, 28, 94)
cts$amacrine <- c(14, 23, 38) 
cts$`bipolar (rod)` <- c(5)
cts$mueller <- c(45)
cts$microglia <- c(74, 82, 83)
cts$monocyte <- c(88)
cts$`retinal ganglion` <- c(7, 70)
cts$pericyte <- c(34)
cts$astrocyte <- c(81)

Hard(er) Calls

Mostly rarer front eye. Adding in the Sanes “Complete Ocular Atlas” predictions

remainder_02 <- remainder_01 %>% filter(!leiden3 %in% (cts %>% unlist()))

remainder_02 %>% 
  group_by(leiden, leiden2) %>% 
  summarise(Count = n()) %>% 
  mutate(Ratio = Count/sum(Count)) %>% 
  filter(Ratio > 0.1)
`summarise()` has grouped output by 'leiden'. You can override using the `.groups` argument.
remainder_02 %>% 
  group_by(leiden3, MajorCellType, CT__sanes_complete_ocular_atlas_SCP2310, CT__sceiad_20250107_full_mmGeneFilter, CT__chen_mrca) %>% 
  summarise(Count =n()) %>% 
  left_join(remainder_02 %>% group_by(leiden3) %>% summarise(lCount = n()), by ='leiden3') %>% 
  mutate(Ratio = Count/lCount) %>% filter(Ratio > 0.05) %>% 
  DT::datatable()
`summarise()` has grouped output by 'leiden3', 'MajorCellType', 'CT__sanes_complete_ocular_atlas_SCP2310', 'CT__sceiad_20250107_full_mmGeneFilter'. You can override using the `.groups` argument.
cts$epithelial <- c(10,29, 77, 91)
cts$fibroblast <- c(31, 92)
cts$`ciliary body` <- c(89)
cts$`t/nk` <- c(96)
cts$mast <- c(104)

Tuned CT Calls

tuned_calls <- bind_rows(easy_calls %>% dplyr::rename(CT=consensus), cts %>% enframe(name = 'CT', value = 'leiden3') %>% unnest())
Warning: `cols` is now required when using `unnest()`.
ℹ Please use `cols = c(leiden3)`.
mm_obs$obs %>% 
  filter(!leiden3 %in% remove_leiden3) %>% 
  left_join(tuned_calls, by = 'leiden3') %>% 
  group_by(CT) %>% 
  sample_n(5000, replace = TRUE) %>% 
  unique() %>% 
  ggplot(aes(x=umap1,y=umap2)) +
  scattermore::geom_scattermore(aes(color = CT), pointsize = 1.2, alpha = 0.5) +
  ggrepel::geom_label_repel(data = . %>% group_by(CT) %>% 
                              summarise(umap1 = median(umap1),
                                        umap2 = median(umap2)),
                            aes(label = CT, color = CT)) +
  scale_color_manual(values = c(pals::alphabet2(), pals::glasbey()) %>% unname()) + 
  cowplot::theme_cowplot() + theme(legend.position = "none") +
  ggtitle("Tuned CT")

HRCT

Photoreceptors

diff_mm <- data.table::fread("/Users/mcgaugheyd/git/scEiaD_modeling/data/mm111_mature_eye_20250114_2000hvg_100e_20l.difftesting.leiden3.csv.gz")
conv_table <- AnnotationDbi::select(org.Mm.eg.db::org.Mm.eg.db,
                                    keys=gsub('\\.\\d+','',unique(diff_mm$names)),
                                    columns=c("ENSEMBL","SYMBOL", "GENENAME", "ENTREZID"), keytype="ENSEMBL")
'select()' returned 1:many mapping between keys and columns
tib <- diff_mm %>% 
  left_join(tuned_calls, by = c("base" = 'leiden3')) %>%  
  filter(CT %in% c("cone","rod")) %>% 
  mutate(ENSEMBL = gsub("\\.\\d+","",names)) %>% 
  left_join(conv_table) %>% 
  filter(SYMBOL %in% str_to_title(c('ARR3','OPN1LW','OPN1SW','RHO', 'OPN1MW', 'RCVRN',"CRX","PROM1"))) %>% 
  select(SYMBOL, base, logfoldchanges) %>% 
  pivot_wider(values_from = logfoldchanges, names_from = base)
Joining with `by = join_by(ENSEMBL)`Warning: Detected an unexpected many-to-many relationship between `x` and `y`.
mat <- tib %>% select(-1) %>% as.matrix()
row.names(mat) <- tib %>% pull(1)

col_fun = circlize::colorRamp2(c(-5, 0, 5), c("blue", "white", "red"))
ComplexHeatmap::Heatmap(mat, col=col_fun)

hrct <- list()
hrct$`cone (s)` <- c(18)
hrct$`cone (ml)` <- c(28,66,30,94)

mm_obs$obs %>% 
  left_join(hrct %>% enframe(name = 'CT', value = 'leiden3') %>% unnest(), by ='leiden3') %>% 
  filter(!is.na(CT)) %>% 
  group_by(CT) %>% 
  sample_n(5000, replace = TRUE) %>% 
  unique() %>% 
  ggplot(aes(x=umap1,y=umap2)) +
  scattermore::geom_scattermore(aes(color = CT), pointsize = 0.8, alpha = 0.5) +
  ggrepel::geom_label_repel(data = . %>% group_by(CT) %>% 
                              summarise(umap1 = median(umap1),
                                        umap2 = median(umap2)),
                            aes(label = CT, color = CT)) +
  scale_color_manual(values = c(pals::alphabet2(), pals::glasbey()) %>% unname()) + 
  cowplot::theme_cowplot() + theme(legend.position = "none") +
  ggtitle("Photoreceptors")
Warning: `cols` is now required when using `unnest()`.
ℹ Please use `cols = c(leiden3)`.

Bipolar

tib <- diff_mm %>% 
  left_join(tuned_calls, by = c("base" = 'leiden3')) %>%  
  filter(grepl("bipolar", CT)) %>% 
  mutate(ENSEMBL = gsub("\\.\\d+","",names)) %>% 
  left_join(conv_table) %>% 
  filter(SYMBOL %in% str_to_title(c('PRKCA','GRM6','GRIK1','NIF3L1',
                                    'LINC00470','DOK5','NELL2','STX18',
                                    'ODF2L','FAM19A4','MEIS2','CALB1', 'FUT4',
                                    'SCG2','LRPPRC','FEZF1' ))) %>% 
  select(SYMBOL, base, logfoldchanges) %>% 
  pivot_wider(values_from = logfoldchanges, names_from = base)
Joining with `by = join_by(ENSEMBL)`Warning: Detected an unexpected many-to-many relationship between `x` and `y`.
mat <- tib %>% select(-1) %>% as.matrix()
row.names(mat) <- tib %>% pull(1)

col_fun = circlize::colorRamp2(c(-5, 0, 5), c("blue", "white", "red"))
ComplexHeatmap::Heatmap(mat, col=col_fun)


hrct$`bipolar (rod)` <- c(5,26)
hrct$`bipolar (off)` <- c(19,32,46,52)
hrct$`bipolar (on)`<- tuned_calls %>% filter(grepl("bipolar",CT)) %>% pull(leiden3)
hrct$`bipolar (on)` <- hrct$`bipolar (on)`[!hrct$`bipolar (on)` %in% 
                                             c(hrct$`bipolar (rod)`, hrct$`bipolar (off)`)]

mm_obs$obs %>% 
  left_join(hrct %>% enframe(name = 'CT', value = 'leiden3') %>% unnest(), by ='leiden3') %>% 
  filter(grepl("bipolar", CT)) %>% 
  group_by(CT) %>% 
  sample_n(5000, replace = TRUE) %>% 
  unique() %>% 
  ggplot(aes(x=umap1,y=umap2)) +
  scattermore::geom_scattermore(aes(color = CT), pointsize = 0.8, alpha = 0.5) +
  ggrepel::geom_label_repel(data = . %>% group_by(CT) %>% 
                              summarise(umap1 = median(umap1),
                                        umap2 = median(umap2)),
                            aes(label = CT, color = CT)) +
  scale_color_manual(values = c(pals::alphabet2(), pals::glasbey()) %>% unname()) + 
  cowplot::theme_cowplot() + theme(legend.position = "none") +
  ggtitle("Bipolar")
Warning: `cols` is now required when using `unnest()`.
ℹ Please use `cols = c(leiden3)`.

Amacrine

tib <- diff_mm %>% 
  left_join(tuned_calls, by = c("base" = 'leiden3')) %>%  
  filter(grepl("amacrine", CT)) %>% 
  mutate(ENSEMBL = gsub("\\.\\d+","",names)) %>% 
  left_join(conv_table) %>% 
  filter(SYMBOL %in% str_to_title(c('GAD1','GAD2','SLC6A9','NFIA'))) %>% 
  select(SYMBOL, base, logfoldchanges) %>% 
  pivot_wider(values_from = logfoldchanges, names_from = base)
Joining with `by = join_by(ENSEMBL)`Warning: Detected an unexpected many-to-many relationship between `x` and `y`.
mat <- tib %>% select(-1) %>% as.matrix()
row.names(mat) <- tib %>% pull(1)

col_fun = circlize::colorRamp2(c(-5, 0, 5), c("blue", "white", "red"))
ComplexHeatmap::Heatmap(mat, col=col_fun)


hrct$`amacrine (glycinergic)` <- c(47,80,54,42,12,58,72,48)
hrct$`amacrine (gaba/glyci)` <- c(23)
hrct$`amacrine (gabanergic)`<- tuned_calls %>% filter(grepl("amacrine",CT)) %>% pull(leiden3)
hrct$`amacrine (gabanergic)` <- hrct$`amacrine (gabanergic)`[!hrct$`amacrine (gabanergic)` %in% 
                                                               c(hrct$`amacrine (glycinergic)`, hrct$`amacrine (gaba/glyci)`)]

mm_obs$obs %>% 
  left_join(hrct %>% enframe(name = 'CT', value = 'leiden3') %>% unnest(), by ='leiden3') %>% 
  filter(grepl("amacrine", CT)) %>% 
  group_by(CT) %>% 
  sample_n(5000, replace = TRUE) %>% 
  unique() %>% 
  ggplot(aes(x=umap1,y=umap2)) +
  scattermore::geom_scattermore(aes(color = CT), pointsize = 0.8, alpha = 0.5) +
  ggrepel::geom_label_repel(data = . %>% group_by(CT) %>% 
                              summarise(umap1 = median(umap1),
                                        umap2 = median(umap2)),
                            aes(label = CT, color = CT)) +
  scale_color_manual(values = c(pals::alphabet2(), pals::glasbey()) %>% unname()) + 
  cowplot::theme_cowplot() + theme(legend.position = "none") +
  ggtitle("Amacrine")
Warning: `cols` is now required when using `unnest()`.
ℹ Please use `cols = c(leiden3)`.

Horizontal

Didn’t see the H1/H2 distinction with ISL1 / LHX2

All are H1?

tib <- diff_mm %>% 
  left_join(tuned_calls, by = c("base" = 'leiden3')) %>%  
  #filter(grepl("horizont", CT)) %>% 
  mutate(ENSEMBL = gsub("\\.\\d+","",names)) %>% 
  left_join(conv_table) %>% 
  filter(SYMBOL %in% str_to_title(c('LHX1','ISL1','ONECUT1','ONECUT2'))) %>% 
  select(SYMBOL, base, logfoldchanges) %>% 
  pivot_wider(values_from = logfoldchanges, names_from = base)
Joining with `by = join_by(ENSEMBL)`Warning: Detected an unexpected many-to-many relationship between `x` and `y`.
mat <- tib %>% select(-1) %>% as.matrix()
row.names(mat) <- tib %>% pull(1)

col_fun = circlize::colorRamp2(c(-5, 0, 5), c("blue", "white", "red"))
ComplexHeatmap::Heatmap(mat, col=col_fun)

Retinal Ganglion

Couldn’t make any sense of them using the markers I curated in “Human_Mature_eye_full__stage4_neural.Rmd”

tib <- diff_mm %>% 
  left_join(tuned_calls, by = c("base" = 'leiden3')) %>%  
  filter(CT == 'retinal ganglion') %>% 
  mutate(ENSEMBL = gsub("\\.\\d+","",names)) %>% 
  left_join(conv_table) %>% 
  filter(SYMBOL %in% str_to_title(c('TPBG','TBR1','FABP4','CHRNA2', 'LMO2',
                                    'EOMES','SSTR2','FOXP2','FOXP1','PRR35','CARTPT',
                                    'CDKN2A','ARPP21','OPN4', 'NEFM',
                                    'TUBB3'))) %>% 
  select(SYMBOL, base, logfoldchanges) %>% 
  pivot_wider(values_from = logfoldchanges, names_from = base)
Joining with `by = join_by(ENSEMBL)`Warning: Detected an unexpected many-to-many relationship between `x` and `y`.
mat <- tib %>% select(-1) %>% as.matrix()
row.names(mat) <- tib %>% pull(1)

col_fun = circlize::colorRamp2(c(-5, 0, 5), c("blue", "white", "red"))
ComplexHeatmap::Heatmap(mat, col=col_fun)

NA
NA

Immune



tib <- diff_mm %>% 
  left_join(tuned_calls, by = c("base" = 'leiden3')) %>%  
  filter(grepl("mast|mono|nk|microglia",CT)) %>% 
  mutate(ENSEMBL = gsub("\\.\\d+","",names)) %>% 
  left_join(conv_table) %>% 
  filter(SYMBOL %in% str_to_title(c("CD68",
                                    "TMEM119", "Olfml3", # monocyte
                                    "Emilin2", "IL1RL1", # mast
                                    "CD4", #tcell
                                    "CD163",# macrophaege
                                    "CD14"))) %>% 
  mutate(base = as.character(base)) %>% 
  mutate(base = case_when(grepl("mast|mono|nk|microglia", CT) ~ paste0(base, ' - ', CT),
                          TRUE ~ base)) %>% 
  select(SYMBOL, base, logfoldchanges) %>% 
  pivot_wider(values_from = logfoldchanges, names_from = base)
Joining with `by = join_by(ENSEMBL)`Warning: Detected an unexpected many-to-many relationship between `x` and `y`.
mat <- tib %>% select(-1) %>% as.matrix()
row.names(mat) <- tib %>% pull(1)

col_fun = circlize::colorRamp2(c(-5, 0, 5), c("blue", "white", "red"))
ComplexHeatmap::Heatmap(mat, col=col_fun)


hrct$macrophage <- c(74, 83)

Tuned CT Calls

hr_tuned_calls <- tuned_calls %>% left_join(
  hrct %>% 
    enframe(name = 'hrCT', value = 'leiden3') %>% 
    unnest(),
  by = 'leiden3'
) %>% 
  mutate(hrCT = case_when(is.na(hrCT) ~ CT,
                          TRUE ~ hrCT))
Warning: `cols` is now required when using `unnest()`.
ℹ Please use `cols = c(leiden3)`.
mm_obs$obs %>% 
  filter(!leiden3 %in% remove_leiden3) %>% 
  left_join(hr_tuned_calls, by = 'leiden3') %>% 
  group_by(CT) %>% 
  sample_n(5000, replace = TRUE) %>% 
  unique() %>% 
  ggplot(aes(x=umap1,y=umap2)) +
  scattermore::geom_scattermore(aes(color = hrCT), pointsize = 1.2, alpha = 0.5) +
  ggrepel::geom_label_repel(data = . %>% group_by(CT, hrCT) %>% 
                              summarise(umap1 = median(umap1),
                                        umap2 = median(umap2)),
                            aes(label = hrCT, color = hrCT)) +
  scale_color_manual(values = c(pals::alphabet2(), pals::glasbey()) %>% unname()) + 
  cowplot::theme_cowplot() + theme(legend.position = "none") +
  ggtitle("Tuned CT")

Stage 4

Output tuned calls, re-run scANVI modeling to finalize CT model. Like the human mature eye models, the covariates (ribosome, mito, etc) were removed from the modelling - this, anecdotally, modestly make the models a little bit worse but will substantially make it easier to apply new data as I don’t have to fuss about with trying to also create the same covariate fields.

Output barcodes

output <- mm_obs$obs %>% 
  filter(!leiden3 %in% remove_leiden3) %>% 
  left_join(hr_tuned_calls, by = 'leiden3') %>% 
  # dplyr::rename(tunedCT = CT,
  #               tuned_hrCT = hrCT) %>% 
  select(-Count, -Ratio) %>% 
  relocate(barcode, CellType, MajorCellType, `CT`, `hrCT`) %>% 
  group_by(across(c(-umap1, -umap2))) %>% summarise(umap1 = mean(umap1), umap2 = mean(umap2)) 
`summarise()` has grouped output by 'barcode', 'CellType', 'MajorCellType', 'CT', 'hrCT', 'background_fraction', 'cell_probability', 'cell_size', 'droplet_efficiency', 'n_genes_by_counts', 'total_counts', 'total_counts_mt', 'pct_counts_mt', 'n_genes', 'n_counts', '_scvi_batch', '_scvi_labels', 'solo_doublet', 'solo_score', 'sample_accession', 'library_layout', 'reference', 'kb_tech', 'umi', 'workflow', 'kb_sum', 'organism', 'platform', 'study_accession', 'tissue', 'covariate', 'integration_group', 'tissuenote', 'source', 'bam10x', 'biosample', 'organ', 'sex', 'biosample_title', 'batch', 'age', 'capture_type', 'SubCellType', 'capture_covariate', 'total_counts_ribo', 'pct_counts_ribo', 'total_counts_protocadherin', 'pct_counts_protocadherin', 'leiden', 'leiden2', 'leiden3', 'leiden5', 'MCT_scANVI', 'MCT_scANVI_max_score', 'side'. You can override using the `.groups` argument.
#output %>% 
#  write_csv(file = "~/git/scEiaD_modeling/data/mm111_adult_eye_tuned_CT_calls.20250120.obs.csv.gz")

set.seed(20250120)
mm111_full_eye_ref_bcs2 <- output %>% 
  group_by(study_accession, hrCT) %>% 
  sample_n(500, replace = TRUE) %>% 
  unique()

mm111_full_eye_query_bcs2 <- output %>%
  filter(!barcode %in% mm111_full_eye_ref_bcs2$barcode)

#mm111_full_eye_ref_bcs2$barcode %>% write(gzfile('~/git/scEiaD_modeling/data/mm111_mature_eye_ref_bcs.full.20250120.stage4.csv.gz'))

#mm111_full_eye_query_bcs2$barcode %>% write(gzfile('~/git/scEiaD_modeling/data/mm111_mature_eye_query_bcs.full.20250120.stage4.csv.gz'))
cd /data/OGVFB_BG/scEiaD/2024_02_28/snakeout/mm111_mature_eye_full/stage4
source /data/$USER/conda/etc/profile.d/conda.sh && source /data/$USER/conda/etc/profile.d/mamba.sh
mamba activate rapids_singlecell
python ~/git/scEiaD_modeling/workflow/scripts/append_obs.py ../../../mm111.adata.solo.20240827.h5ad /home/mcgaugheyd/git/scEiaD_modeling/data/mm111_adult_eye_tuned_CT_calls.20250120.obs.csv.gz  mm111_mature_eye_20250120_full_2000hvg_100e_20l_stage4.h5ad --transfer_columns CT,hrCT

note - adding more epochs (was previously hardcoded to 50) to the scanvi modelling step seems to make a noticeable improvement in accuracy. I had previously set this to 50 because it is such a slow step - especially when running it across the (much larger) human eye dataset.

mm_obs4 <-pull_obs('~/data/scEiaD_modeling/mm111_mature_eye_full/mm111_mature_eye_20250120_stage4_noCov_150epo_2000hvg_50e_20l.obs.csv.gz', machine_label = 'scANVI_hrCT', label = 'hrCT')
`summarise()` has grouped output by 'leiden3'. You can override using the `.groups` argument.`summarise()` has grouped output by 'leiden3'. You can override using the `.groups` argument.`summarise()` has grouped output by 'leiden3'. You can override using the `.groups` argument.`summarise()` has grouped output by 'leiden3'. You can override using the `.groups` argument.`summarise()` has grouped output by 'leiden3'. You can override using the `.groups` argument.`summarise()` has grouped output by 'leiden3'. You can override using the `.groups` argument.`summarise()` has grouped output by 'leiden3'. You can override using the `.groups` argument.

Tuning

Group by leiden3, cell type and rename cell types in the minority (5% in this case) to the majority cell type call

# retains CT calls >= 0.05 of a cluster. anything below gets changed to the dominant ct
mm_nobs_s4_cleaning <- mm_obs4$obs %>% 
  group_by(leiden3, scANVI_hrCT) %>% 
  summarise(Count = n()) %>%
  mutate(Ratio = Count / sum(Count)) %>% 
  mutate(dominant_celltype = scANVI_hrCT[which.max(Count)]) %>% 
  mutate(CTc = case_when(Ratio < 0.05 ~ dominant_celltype,
                         TRUE ~ scANVI_hrCT))
`summarise()` has grouped output by 'leiden3'. You can override using the `.groups` argument.
mm_nobs_s4 <- mm_obs4$obs %>% 
  left_join(mm_nobs_s4_cleaning %>% 
              select(leiden3, scANVI_hrCT, CTc), by = c("leiden3","scANVI_hrCT"))

# quick compare count diffs
mm_nobs_s4 %>% group_by(hrCT, CTc) %>% mcHelpeRs::sum_rat() %>% data.frame

UMAPs

mm_nobs_s4 %>% 
  group_by(leiden3) %>% 
  sample_n(1000, replace = TRUE) %>% 
  unique() %>% 
  ggplot(aes(x=umap1,y=umap2)) +
  scattermore::geom_scattermore(aes(color = hrCT), pointsize = 0.8, alpha = 0.9) +
  ggrepel::geom_label_repel(data = . %>% group_by(hrCT) %>% 
                              summarise(umap1 = median(umap1),
                                        umap2 = median(umap2)),
                            aes(label = hrCT, color = hrCT)) +
  scale_color_manual(values = c(pals::alphabet2(), pals::glasbey()) %>% unname()) + 
  cowplot::theme_cowplot() + theme(legend.position = "none") +
  ggtitle("Stage 4 Tuned CT")


mm_nobs_s4 %>% 
  group_by(leiden3) %>% 
  sample_n(1000, replace = TRUE) %>% 
  unique() %>% 
  ggplot(aes(x=umap1,y=umap2)) +
  scattermore::geom_scattermore(aes(color = CTc), pointsize = 0.8, alpha = 0.9) +
  ggrepel::geom_label_repel(data = . %>% group_by(CTc) %>% 
                              summarise(umap1 = median(umap1),
                                        umap2 = median(umap2)),
                            aes(label = CTc, color = CTc)) +
  scale_color_manual(values = c(pals::alphabet2(), pals::glasbey()) %>% unname()) + 
  cowplot::theme_cowplot() + theme(legend.position = "none") +
  ggtitle("Stage 4 Tuned scANVI CT")

UMAP, split by cell type

mm_obs4$obs %>% 
  group_by(leiden3) %>% 
  sample_n(1000, replace = TRUE) %>% 
  unique() %>% 
  ggplot(aes(x=umap1,y=umap2)) +
  scattermore::geom_scattermore(aes(color = scANVI_hrCT), pointsize = 2.8, alpha = 0.9) +
  scale_color_manual(values = c(pals::alphabet2(), pals::glasbey()) %>% unname()) + 
  cowplot::theme_cowplot() + theme(legend.position = "none") +
  ggtitle("Stage 4 scANVI CT") +
  facet_wrap(~scANVI_hrCT)

mm_nobs_s4 %>% 
  group_by(leiden3) %>% 
  sample_n(1000, replace = TRUE) %>% 
  unique() %>% 
  ggplot(aes(x=umap1,y=umap2)) +
  scattermore::geom_scattermore(aes(color = CTc), pointsize = 2.8, alpha = 0.9) +
  scale_color_manual(values = c(pals::alphabet2(), pals::glasbey()) %>% unname()) + 
  cowplot::theme_cowplot() + theme(legend.position = "none") +
  ggtitle("Stage 4 scANVI CT") +
  facet_wrap(~CTc)

Confusion Matrix

From Author Label (after my manual nomenclature normalization)

machine_label = 'scANVI_hrCT'; label = 'MajorCellType'
mm_nobs_s4 %>% 
  group_by(.data[[label]],.data[[machine_label]]) %>% 
  summarise(Count = n()) %>% 
  mutate(Ratio = Count/sum(Count)) %>% 
  ggplot(aes(x=.data[[label]],y=.data[[machine_label]],fill=Ratio, label = round(Ratio, 2))) + 
  geom_tile() + 
  shadowtext::geom_shadowtext() + 
  cowplot::theme_cowplot() +
  scale_fill_viridis_c(begin = 0, end = 1) +
  theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1)) 
`summarise()` has grouped output by 'MajorCellType'. You can override using the `.groups` argument.

machine_label = 'CTc'; label = 'hrCT'
mm_nobs_s4 %>% 
  group_by(.data[[label]],.data[[machine_label]]) %>% 
  summarise(Count = n()) %>% 
  mutate(Ratio = Count/sum(Count)) %>% 
  ggplot(aes(x=.data[[label]],y=.data[[machine_label]],fill=Ratio, label = round(Ratio, 2))) + 
  geom_tile() + 
  shadowtext::geom_shadowtext() + 
  cowplot::theme_cowplot() +
  scale_fill_viridis_c(begin = 0, end = 1) +
  theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1)) 
`summarise()` has grouped output by 'hrCT'. You can override using the `.groups` argument.

mm_nobs_s4 %>% write_csv("~/data/scEiaD_modeling/mm111_mature_eye_full/mm111_mature_eye_20250120_stage4_noCov_150epo_2000hvg_50e_20l.tunedStage4.v01.obs.csv.gz")
sessionInfo()
R version 4.4.1 (2024-06-14)
Platform: aarch64-apple-darwin20
Running under: macOS Sonoma 14.7.2

Matrix products: default
BLAS:   /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.0

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

time zone: America/New_York
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] ggtree_3.12.0   fstcore_0.9.18  lubridate_1.9.3 forcats_1.0.0   stringr_1.5.1   dplyr_1.1.4    
 [7] purrr_1.0.2     readr_2.1.5     tidyr_1.3.1     tibble_3.2.1    ggplot2_3.5.1   tidyverse_2.0.0

loaded via a namespace (and not attached):
  [1] RColorBrewer_1.1-3          shape_1.4.6.1               rstudioapi_0.16.0          
  [4] jsonlite_1.8.8              magrittr_2.0.3              magick_2.8.5               
  [7] farver_2.1.2                rmarkdown_2.27              GlobalOptions_0.1.2        
 [10] fs_1.6.4                    zlibbioc_1.50.0             vctrs_0.6.5                
 [13] Cairo_1.6-2                 memoise_2.0.1               DelayedMatrixStats_1.26.0  
 [16] htmltools_0.5.8.1           S4Arrays_1.4.1              BiocNeighbors_1.22.0       
 [19] SparseArray_1.4.8           gridGraphics_0.5-1          sass_0.4.9                 
 [22] bslib_0.8.0                 htmlwidgets_1.6.4           cachem_1.1.0               
 [25] igraph_2.0.3                iterators_1.0.14            lifecycle_1.0.4            
 [28] pkgconfig_2.0.3             rsvd_1.0.5                  Matrix_1.7-0               
 [31] R6_2.5.1                    fastmap_1.2.0               clue_0.3-65                
 [34] GenomeInfoDbData_1.2.12     MatrixGenerics_1.16.0       digest_0.6.36              
 [37] aplot_0.2.3                 colorspace_2.1-1            patchwork_1.2.0            
 [40] AnnotationDbi_1.66.0        S4Vectors_0.42.1            dqrng_0.4.1                
 [43] irlba_2.3.5.1               crosstalk_1.2.1             GenomicRanges_1.56.1       
 [46] RSQLite_2.3.7               beachmat_2.20.0             labeling_0.4.3             
 [49] org.Mm.eg.db_3.19.1         fansi_1.0.6                 timechange_0.3.0           
 [52] httr_1.4.7                  abind_1.4-5                 compiler_4.4.1             
 [55] doParallel_1.0.17           bit64_4.0.5                 withr_3.0.0                
 [58] BiocParallel_1.38.0         DBI_1.2.3                   R.utils_2.12.3             
 [61] maps_3.4.2                  DelayedArray_0.30.1         rjson_0.2.21               
 [64] bluster_1.14.0              tools_4.4.1                 ape_5.8                    
 [67] fst_0.9.8                   R.oo_1.26.0                 glue_1.7.0                 
 [70] nlme_3.1-165                shadowtext_0.1.4            grid_4.4.1                 
 [73] cluster_2.1.6               generics_0.1.3              gtable_0.3.5               
 [76] tzdb_0.4.0                  R.methodsS3_1.8.2           data.table_1.16.99         
 [79] hms_1.1.3                   BiocSingular_1.20.0         ScaledMatrix_1.12.0        
 [82] metapod_1.12.0              utf8_1.2.4                  XVector_0.44.0             
 [85] BiocGenerics_0.50.0         foreach_1.5.2               ggrepel_0.9.5              
 [88] pillar_1.9.0                vroom_1.6.5                 yulab.utils_0.1.5          
 [91] limma_3.60.4                pals_1.9                    circlize_0.4.16            
 [94] treeio_1.28.0               lattice_0.22-6              bit_4.0.5                  
 [97] tidyselect_1.2.1            ComplexHeatmap_2.20.0       SingleCellExperiment_1.26.0
[100] locfit_1.5-9.10             Biostrings_2.72.1           scuttle_1.14.0             
[103] knitr_1.48                  IRanges_2.38.1              edgeR_4.2.1                
[106] SummarizedExperiment_1.34.0 scattermore_1.2             stats4_4.4.1               
[109] xfun_0.48                   Biobase_2.64.0              statmod_1.5.0              
[112] matrixStats_1.3.0           DT_0.33                     stringi_1.8.4              
[115] UCSC.utils_1.0.0            lazyeval_0.2.2              ggfun_0.1.5                
[118] yaml_2.3.10                 evaluate_0.24.0             codetools_0.2-20           
[121] ggplotify_0.1.2             cli_3.6.3                   munsell_0.5.1              
[124] jquerylib_0.1.4             dichromat_2.0-0.1           Rcpp_1.0.13                
[127] GenomeInfoDb_1.40.1         mapproj_1.2.11              png_0.1-8                  
[130] parallel_4.4.1              blob_1.2.4                  scran_1.32.0               
[133] sparseMatrixStats_1.16.0    viridisLite_0.4.2           tidytree_0.4.6             
[136] metamoRph_0.2.1             scales_1.3.0                crayon_1.5.3               
[139] GetoptLong_1.0.5            rlang_1.1.4                 KEGGREST_1.44.1            
[142] cowplot_1.1.3              
LS0tCnRpdGxlOiAiTW91c2UgQWR1bHQgRXllIgphdXRob3I6ICJEYXZpZCBNY0dhdWdoZXkiCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKb3V0cHV0OgogaHRtbF9ub3RlYm9vazoKICB0aGVtZTogZmxhdGx5CiAgdG9jOiB0cnVlCiAgdG9jX2Zsb2F0OiB0cnVlCiAgY29kZV9mb2xkaW5nOiBzaG93Ci0tLQoKIyBTdGFnZSAxClB1bGwgaW4gbW91c2UgY2VsbCBtZXRhZGF0YSBhbmQgcmV0YWluIGV5ZSBjZWxscy4KCk91dHB1dCB0aGUgYmFyY29kZXMuCgpgYGB7ciwgZXhlYyA9IEZBTFNFfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKc2FtcGxlX21ldGEgPC0gZGF0YS50YWJsZTo6ZnJlYWQoJ34vZ2l0L3NjRWlhRF9xdWFudC9zYW1wbGVfbWV0YS5zY0VpYURfdjEuMjAyNF8wMl8yOC4wMS50c3YuZ3onKQpjZWxsX21ldGEgPC0gZGF0YS50YWJsZTo6ZnJlYWQoJ34vZGF0YS9zY0VpYURfbW9kZWxpbmcvbW0xMTEuYWRhdGEuc29sby4yMDI0MDgyNy5vYnMuY3N2Lmd6JylbLC0xXSAlPiUgCiAgcmVsb2NhdGUoYmFyY29kZSkgJT4lIAogIGZpbHRlcihzb2xvX2RvdWJsZXQgPT0gIkZBTFNFIikKc2NlaWFkX21ldGEgPC0gZnN0OjpyZWFkX2ZzdCgnfi9kYXRhL3NjRWlhRF8yMDIyXzAyL21ldGFfZmlsdGVyLmZzdCcpCmNlbGxfbWV0YSA8LSBjZWxsX21ldGEgJT4lIAogIGxlZnRfam9pbihzY2VpYWRfbWV0YSAlPiUgc2VsZWN0KGJhcmNvZGUgPSBCYXJjb2RlLCBDZWxsVHlwZV9wcmVkaWN0KSwgYnkgPSAnYmFyY29kZScpCm1tMTExX2V5ZSA8LSBjZWxsX21ldGEgJT4lIAogIGZpbHRlcigKICAgIG9yZ2FuID09ICdFeWUnLAogICAgb3JnYW5pc20gPT0gJ011cyBtdXNjdWx1cycsCiAgICAhZ3JlcGwoIl4jIiwgc2FtcGxlX2FjY2Vzc2lvbiksCiAgICBzb3VyY2UgPT0gJ1Rpc3N1ZScpCgojbW0xMTFfZXllJGJhcmNvZGUgJT4lIHdyaXRlKGd6ZmlsZSgnfi9naXQvc2NFaWFEX21vZGVsaW5nL2RhdGEvbW0xMTFfZXllX2Jjcy4yMDI1MDExNS5jc3YuZ3onKSkKYGBgCgojIFN0YWdlIDIKClVzZSBleGlzdGluZyBtb2RlbHMgdG8gcHJlZGljdCBjZWxsIHR5cGVzOgoKMS4gQ2hlbiBIUkNBCjIuIENoZW4gTVJDQQozLiBTYW5lcyAiQ29tcGxldGUgT2N1bGFyIEF0bGFzIiAod2hpY2ggSSB3aWxsIGR1YiBDT0EpCjQuIHNjRWlhRCBuZXVyYWwgMjAyNTAxMDcKNS4gc2NFaWFEIG5vbi1uZXVyYWwgMjAyNTAxMDcKNi4gc2NFaWFEIGZ1bGwgb2N1bGFyIDIwMjUwMTA3CjcuIHNjRWlhRCBmdWxsIG9jdWxhciAyMDI1MDEwNyAibW1HZW5lRmlsdGVyIiAoZ2VuZXMgbmFtZXMgYXZhaWxhYmxlIGZvciBIVkcgZmlsdGVyZWQgZG93biB0byBvbmVzIHRoYXQgbWF0Y2ggYWNyb3NzIEhDT1AgbW91c2UvaHVtYW4pCgpgYGB7YmFzaCBiaW93dWxmMiwgZXZhbCA9IEZBTFNFfQojIHJzeW5jIHRoZSBjc3YgdG8gYmlvd3VsZjIgKG5vdCBzaG93bikKY2QgL2RhdGEvT0dWRkJfQkcvc2NFaWFELzIwMjRfMDJfMjgvc25ha2VvdXQvbW0xMTFfbWF0dXJlX2V5ZV9mdWxsCnNpbnRlcmFjdGl2ZSAtLW1lbT0xMjhHIC0tdGltZT04OjAwOjAwICAtLWdyZXM9Z3B1OmExMDA6MQpzb3VyY2UgL2RhdGEvJFVTRVIvY29uZGEvZXRjL3Byb2ZpbGUuZC9jb25kYS5zaCAmJiBzb3VyY2UgL2RhdGEvJFVTRVIvY29uZGEvZXRjL3Byb2ZpbGUuZC9tYW1iYS5zaAptYW1iYSBhY3RpdmF0ZSByYXBpZHNfc2luZ2xlY2VsbApiYXNoIGN0X3Byb2plY3Rpb25fY2FsbC5zaApgYGAKCmBgYHtiYXNoIGxvY2FsLCBldmFsID0gRkFMU0V9CmNkIC9Vc2Vycy9tY2dhdWdoZXlkL2RhdGEvc2NFaWFEX21vZGVsaW5nL21tMTExX21hdHVyZV9leWVfZnVsbApyc3luYyAtUHJhdiBoMjovZGF0YS9PR1ZGQl9CRy9zY0VpYUQvMjAyNF8wMl8yOC9zbmFrZW91dC9tbTExMV9tYXR1cmVfZXllX2Z1bGwvKkNUX3Byb2plY3Rpb25zXzIwMTIwMTE0KiAuCmBgYAoKIyMgQ2hlbiBNUkNBCmBgYHtyfQpjdHBfbW0xMTFfX21yY2EgPC0gZGF0YS50YWJsZTo6ZnJlYWQoJy9Vc2Vycy9tY2dhdWdoZXlkL2RhdGEvc2NFaWFEX21vZGVsaW5nL21tMTExX21hdHVyZV9leWVfZnVsbC9tbTExMS5leWUubW91c2VfQ1RfcHJvamVjdGlvbnNfMjAxMjAxMTUuY3N2Lmd6JykgJT4lIHNlbGVjdCgtMTcpICU+JSBsZWZ0X2pvaW4oc2NlaWFkX21ldGEgJT4lIHNlbGVjdChiYXJjb2RlID0gQmFyY29kZSwgQ2VsbFR5cGVfcHJlZGljdCksIGJ5ID0gJ2JhcmNvZGUnKSAlPiUgCiAgIyBvbmx5IGxvb2sgYXQgPj0gMTAgZGF5IGNlbGxzCiAgbXV0YXRlKGFnZSA9IGFzLm51bWVyaWMoYWdlKSkgJT4lIAogIGZpbHRlcihhZ2UgPj0gMTAgfCBpcy5uYShhZ2UpKQoKbGFiZWwgPC0gJ01ham9yQ2VsbFR5cGUnCm1hY2hpbmVfbGFiZWwgPC0gJ0NUX19jaGVuX21yY2EnCm1vc3RfY29tbW9uX21pc2xhYmVsIDwtIGN0cF9tbTExMV9fbXJjYSAlPiUgCiAgZ3JvdXBfYnkoLmRhdGFbW2xhYmVsXV0sLmRhdGFbW21hY2hpbmVfbGFiZWxdXSkgJT4lIAogIHN1bW1hcmlzZShDb3VudCA9IG4oKSkgJT4lIAogIGFycmFuZ2UoLmRhdGFbW2xhYmVsXV0sIC1Db3VudCkgJT4lIAogIHNsaWNlX21heChvcmRlcl9ieSA9IENvdW50LCBuID0gMikgJT4lIAogIGZpbHRlciguZGF0YVtbbGFiZWxdXSAhPSAuZGF0YVtbbWFjaGluZV9sYWJlbF1dKQoKY3RwX21tMTExX19tcmNhICU+JSAKICBtdXRhdGUodGYgPSBjYXNlX3doZW4oLmRhdGFbW2xhYmVsXV0gPT0gLmRhdGFbW21hY2hpbmVfbGFiZWxdXSB+IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICAgIFRSVUUgfiBGQUxTRSkpICU+JSAKICBncm91cF9ieSguZGF0YVtbbGFiZWxdXSkgJT4lIAogIHN1bW1hcmlzZShhY2N1cmFjeSA9IHN1bSh0ZikvbGVuZ3RoKHRmKSkgJT4lIAogIGFycmFuZ2UoLmRhdGFbW2xhYmVsXV0pICU+JSAKICBsZWZ0X2pvaW4obW9zdF9jb21tb25fbWlzbGFiZWwgJT4lIAogICAgICAgICAgICAgIHNlbGVjdChhbnlfb2YobGFiZWwpLCAKICAgICAgICAgICAgICAgICAgICAgYG1vc3QgY29tbW9uIG1pc2xhYmVsYCA9IGFueV9vZihtYWNoaW5lX2xhYmVsKSwKICAgICAgICAgICAgICAgICAgICAgYG1pc2xhYmVsIGNvdW50YCA9IENvdW50KSkKYGBgCgojIyMgVU1BUApgYGB7ciwgZmlnLndpZHRoPTEyLGZpZy5oZWlnaHQ9MTJ9CnVtYXAxID0gJ3VtYXAxX2NoZW5fbXJjYScKdW1hcDIgPSAndW1hcDJfY2hlbl9tcmNhJwpjdCA9ICdDVF9fY2hlbl9tcmNhJwpjb2xvciA9ICdDVF9fY2hlbl9tcmNhJwpzY29yZSA8LSAnQ1RfX2NoZW5fbXJjYV9fbWF4X3Njb3JlJwp1bWFwX2N0X3Bsb3R0ZXIgPC0gZnVuY3Rpb24ob2JqLCB1bWFwMSwgdW1hcDIsIGN0LCBjb2xvciwgbGFiZWxzID0gVFJVRSl7CiAgcGxvdCA8LSBvYmogJT4lIAogICAgbXV0YXRlKGxlaWRlbiA9IGFzLmZhY3RvcihsZWlkZW4pKSAlPiUgCiAgICBnZ3Bsb3QoYWVzKHg9LmRhdGFbW3VtYXAxXV0seT0uZGF0YVtbdW1hcDJdXSkpICsKICAgIHNjYXR0ZXJtb3JlOjpnZW9tX3NjYXR0ZXJtb3JlKGFlcyhjb2xvciA9IC5kYXRhW1tjb2xvcl1dKSwgcG9pbnRzaXplID0gMC44LCBhbHBoYSA9IDAuNSkgKwogICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMocGFsczo6YWxwaGFiZXQyKCksIHBhbHM6OmdsYXNiZXkoKSwgcGFsczo6a2VsbHkoKSwgcGFsczo6YWxwaGFiZXQoKSkgJT4lCiAgICAgICAgICAgICAgICAgICAgICAgICB1bm5hbWUoKSkgKyAKICAgIGNvd3Bsb3Q6OnRoZW1lX2Nvd3Bsb3QoKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKICBpZiAobGFiZWxzKXsKICAgIHBsb3QgPC0gcGxvdCArIGdncmVwZWw6Omdlb21fbGFiZWxfcmVwZWwoZGF0YSA9IC4gJT4lIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyb3VwX2J5KC5kYXRhW1tjdF1dKSAlPiUgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3VtbWFyaXNlKGFjcm9zcyhhbGxfb2YoYyh1bWFwMSx1bWFwMikpLCBtZWRpYW4pKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWVzKGxhYmVsID0gLmRhdGFbW2N0XV0sIGNvbG9yID0gLmRhdGFbW2NvbG9yXV0pKSAKICAgIAogIH0gCiAgcHJpbnQocGxvdCkKfQoKdW1hcF9zY29yZV9wbG90dGVyIDwtIGZ1bmN0aW9uKG9iaiwgdW1hcDEsIHVtYXAyLCBjdCwgc2NvcmUpewogIG9iaiAlPiUgCiAgICBnZ3Bsb3QoYWVzKHg9LmRhdGFbW3VtYXAxXV0seT0uZGF0YVtbdW1hcDJdXSkpICsKICAgIHNjYXR0ZXJtb3JlOjpnZW9tX3NjYXR0ZXJtb3JlKGFlcyhjb2xvciA9IC5kYXRhW1tzY29yZV1dKSwgcG9pbnRzaXplID0gMC44LCBhbHBoYSA9IDAuNSkgKwogICAgZ2dyZXBlbDo6Z2VvbV9sYWJlbF9yZXBlbChkYXRhID0gLiAlPiUgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXBfYnkoLmRhdGFbW2N0XV0pICU+JSAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdW1tYXJpc2UoYWNyb3NzKGFsbF9vZihjKHVtYXAxLHVtYXAyKSksIG1lZGlhbikpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhZXMobGFiZWwgPSAuZGF0YVtbY3RdXSkpICsgCiAgICBzY2FsZV9jb2xvcl92aXJpZGlzX2MoKSArCiAgICBjb3dwbG90Ojp0aGVtZV9jb3dwbG90KCkgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCn0KdW1hcF9jdF9wbG90dGVyKGN0cF9tbTExMV9fbXJjYSwgdW1hcDEsIHVtYXAyLCBjdCwgY29sb3IpCnVtYXBfY3RfcGxvdHRlcihjdHBfbW0xMTFfX21yY2EsIHVtYXAxLCB1bWFwMiwgJ2xlaWRlbicsICdsZWlkZW4nKQp1bWFwX3Njb3JlX3Bsb3R0ZXIoY3RwX21tMTExX19tcmNhLCB1bWFwMSwgdW1hcDIsIGN0LCBzY29yZSkKYGBgCgpgYGB7ciwgZmlnLndpZHRoPTEyLGZpZy5oZWlnaHQ9MTJ9CnVtYXAxID0gJ3VtYXAxX2NoZW5fbXJjYScKdW1hcDIgPSAndW1hcDJfY2hlbl9tcmNhJwpjdCA9ICdDZWxsVHlwZV9wcmVkaWN0Jwpjb2xvciA9ICdDZWxsVHlwZV9wcmVkaWN0Jwp1bWFwX2N0X3Bsb3R0ZXIoY3RwX21tMTExX19tcmNhLCB1bWFwMSwgdW1hcDIsIGN0LCBjb2xvcikKYGBgCiMjIyBQcm9wb3J0aW9uIG9mIENUIGNhbGxzIGFjcm9zcyBlYWNoIGxlaWRlbiBjbHVzdGVyCiMjIyMgQ2VsbFR5cGUgcHJlZGljdCBmcm9tIHNjRWlhRCAyMDIyICh3ZWIgLyBwdWJsaXNoZWQpCmBgYHtyLCBmaWcuaGVpZ2h0PTMwLCBmaWcud2lkdGg9MTJ9CmN0IDwtICdDZWxsVHlwZV9wcmVkaWN0JwoKYmFyX3Bsb3R0ZXIgPC0gZnVuY3Rpb24oZGF0YSA9IGN0cF9tbTExMSwgYSA9ICdsZWlkZW4nLCBiID0gY3QpewogIGRhdGEgJT4lIAogICAgbXV0YXRlKGxlaWRlbiA9IGFzLmZhY3RvcihsZWlkZW4pKSAlPiUgCiAgICBncm91cF9ieSguZGF0YVtbYV1dLCAuZGF0YVtbYl1dKSAlPiUgCiAgICBzdW1tYXJpc2UoQ291bnQgPSBuKCkpICU+JSAKICAgIG11dGF0ZShSYXRpbyA9IENvdW50IC8gc3VtKENvdW50KSwKICAgICAgICAgICBhY3Jvc3MoYWxsX29mKGIpLCBzdHJpbmdyOjpzdHJfd3JhcCx3aWR0aCA9IDE1KSkgJT4lIAogICAgZmlsdGVyKFJhdGlvID4gMC4wNSkgJT4lIAogICAgZ2dwbG90KGFlcyh4PVJhdGlvLHk9LmRhdGFbW2FdXSwgZmlsbCA9IAogICAgICAgICAgICAgICAgIC5kYXRhW1tiXV0sIGxhYmVsID0gCiAgICAgICAgICAgICAgICAgLmRhdGFbW2JdXSkpICsKICAgIGdlb21fYmFyKHN0YXQ9J2lkZW50aXR5JykgKyAKICAgICNzaGFkb3d0ZXh0OjpnZW9tX3NoYWRvd3RleHQocG9zaXRpb24gPSBwb3NpdGlvbl9zdGFjayh2anVzdCA9IDAuNSksIGNvbG9yID0gJ2JsYWNrJywgYmcuY29sb3VyPSd3aGl0ZScpICsKICAgIGdncmVwZWw6Omdlb21fdGV4dF9yZXBlbChwb3NpdGlvbiA9IHBvc2l0aW9uX3N0YWNrKHZqdXN0ID0gMC41KSwgYmcuY29sb3IgPSAnd2hpdGUnLCBkaXJlY3Rpb24gPSAneCcpICArCiAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKHBhbHM6OmFscGhhYmV0MigpLCBwYWxzOjpnbGFzYmV5KCkpICU+JSB1bm5hbWUoKSkgKyAKICAgIGNvd3Bsb3Q6OnRoZW1lX2Nvd3Bsb3QoKSArCiAgICAjc2NhbGVfeF9jb250aW51b3VzKGxpbWl0cyA9IGMoMCwxLjEpKSArCiAgICBjb29yZF9jYXJ0ZXNpYW4oY2xpcCA9ICJvZmYiKSAKfQpiYXJfcGxvdHRlcihjdHBfbW0xMTFfX21yY2EsIGIgPSBjdCkKYGBgCgojIyMjIENlbGxUeXBlIHByZWRpY3QgZnJvbSBDaGVuIE1SQ0EKYGBge3IsIGZpZy5oZWlnaHQ9MzAsIGZpZy53aWR0aD0xMn0KY3QgPC0gJ0NUX19jaGVuX21yY2EnCmJhcl9wbG90dGVyKGN0cF9tbTExMV9fbXJjYSwgYiA9IGN0KQpgYGAKCgoKIyMgc2NFaWFEIEh1bWFuIE1hdHVyZSBFeWUgRnVsbCAyMDI1MDEwNwpgYGB7cn0KY3RwX21tMTExX3NjZWlhZCA8LSBkYXRhLnRhYmxlOjpmcmVhZCgnL1VzZXJzL21jZ2F1Z2hleWQvZGF0YS9zY0VpYURfbW9kZWxpbmcvbW0xMTFfbWF0dXJlX2V5ZV9mdWxsL21tMTExLmV5ZS5odW1hbl9DVF9wcm9qZWN0aW9uc18yMDEyMDExNS5jc3YuZ3onKSAlPiUgc2VsZWN0KC0xNykgJT4lIGxlZnRfam9pbihzY2VpYWRfbWV0YSAlPiUgc2VsZWN0KGJhcmNvZGUgPSBCYXJjb2RlLCBDZWxsVHlwZV9wcmVkaWN0KSwgYnkgPSAnYmFyY29kZScpJT4lIAogICMgb25seSBsb29rIGF0ID49IDEwIGRheSBjZWxscwogIG11dGF0ZShhZ2UgPSBhcy5udW1lcmljKGFnZSkpICU+JSAKICBmaWx0ZXIoYWdlID49IDEwIHwgaXMubmEoYWdlKSkKbGFiZWwgPC0gJ0NlbGxUeXBlX3ByZWRpY3QnCm1hY2hpbmVfbGFiZWwgPC0gJ0NUX19zY2VpYWRfMjAyNTAxMDdfZnVsbF9tbUdlbmVGaWx0ZXInCm1vc3RfY29tbW9uX21pc2xhYmVsIDwtIGN0cF9tbTExMV9zY2VpYWQgJT4lIAogIGdyb3VwX2J5KC5kYXRhW1tsYWJlbF1dLC5kYXRhW1ttYWNoaW5lX2xhYmVsXV0pICU+JSAKICBzdW1tYXJpc2UoQ291bnQgPSBuKCkpICU+JSAKICBhcnJhbmdlKC5kYXRhW1tsYWJlbF1dLCAtQ291bnQpICU+JSAKICBzbGljZV9tYXgob3JkZXJfYnkgPSBDb3VudCwgbiA9IDIpICU+JSAKICBmaWx0ZXIoLmRhdGFbW2xhYmVsXV0gIT0gLmRhdGFbW21hY2hpbmVfbGFiZWxdXSkKCmN0cF9tbTExMV9zY2VpYWQgJT4lIAogIG11dGF0ZSh0ZiA9IGNhc2Vfd2hlbiguZGF0YVtbbGFiZWxdXSA9PSAuZGF0YVtbbWFjaGluZV9sYWJlbF1dIH4gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgICAgVFJVRSB+IEZBTFNFKSkgJT4lIAogIGdyb3VwX2J5KC5kYXRhW1tsYWJlbF1dKSAlPiUgCiAgc3VtbWFyaXNlKGFjY3VyYWN5ID0gc3VtKHRmKS9sZW5ndGgodGYpKSAlPiUgCiAgYXJyYW5nZSguZGF0YVtbbGFiZWxdXSkgJT4lIAogIGxlZnRfam9pbihtb3N0X2NvbW1vbl9taXNsYWJlbCAlPiUgCiAgICAgICAgICAgICAgc2VsZWN0KGFueV9vZihsYWJlbCksIAogICAgICAgICAgICAgICAgICAgICBgbW9zdCBjb21tb24gbWlzbGFiZWxgID0gYW55X29mKG1hY2hpbmVfbGFiZWwpLAogICAgICAgICAgICAgICAgICAgICBgbWlzbGFiZWwgY291bnRgID0gQ291bnQpKQpgYGAKCiMjIyBVTUFQCmBgYHtyLCBmaWcud2lkdGg9MTIsZmlnLmhlaWdodD0xMn0KdW1hcDEgPSAndW1hcDFfc2NlaWFkXzIwMjUwMTA3X2Z1bGxfbW1HZW5lRmlsdGVyJwp1bWFwMiA9ICd1bWFwMl9zY2VpYWRfMjAyNTAxMDdfZnVsbF9tbUdlbmVGaWx0ZXInCmN0ID0gJ0NUX19zY2VpYWRfMjAyNTAxMDdfZnVsbF9tbUdlbmVGaWx0ZXInCmNvbG9yID0gJ0NUX19zY2VpYWRfMjAyNTAxMDdfZnVsbF9tbUdlbmVGaWx0ZXInCnNjb3JlIDwtICdDVF9fc2NlaWFkXzIwMjUwMTA3X2Z1bGxfbW1HZW5lRmlsdGVyX19tYXhfc2NvcmUnCgoKdW1hcF9jdF9wbG90dGVyKGN0cF9tbTExMV9zY2VpYWQsIHVtYXAxLCB1bWFwMiwgY3QsIGNvbG9yKQp1bWFwX2N0X3Bsb3R0ZXIoY3RwX21tMTExX3NjZWlhZCwgdW1hcDEsIHVtYXAyLCAnbGVpZGVuJywgJ2xlaWRlbicpCnVtYXBfc2NvcmVfcGxvdHRlcihjdHBfbW0xMTFfc2NlaWFkLCB1bWFwMSwgdW1hcDIsIGN0LCBzY29yZSkKYGBgCgpgYGB7ciwgZmlnLndpZHRoPTEyLGZpZy5oZWlnaHQ9MTJ9CnVtYXAxID0gJ3VtYXAxX3NjZWlhZF8yMDI1MDEwN19mdWxsX21tR2VuZUZpbHRlcicKdW1hcDIgPSAndW1hcDJfc2NlaWFkXzIwMjUwMTA3X2Z1bGxfbW1HZW5lRmlsdGVyJwpjdCA9ICdDZWxsVHlwZV9wcmVkaWN0Jwpjb2xvciA9ICdDZWxsVHlwZV9wcmVkaWN0Jwp1bWFwX2N0X3Bsb3R0ZXIoY3RwX21tMTExX3NjZWlhZCwgdW1hcDEsIHVtYXAyLCBjdCwgY29sb3IpCmBgYAojIyMgUHJvcG9ydGlvbiBvZiBDVCBjYWxscyBhY3Jvc3MgZWFjaCBsZWlkZW4gY2x1c3RlcgojIyMjIENlbGxUeXBlIHByZWRpY3QgZnJvbSBzY0VpYUQgMjAyMiAod2ViIC8gcHVibGlzaGVkKQpgYGB7ciwgZmlnLmhlaWdodD0xNSwgZmlnLndpZHRoPTEyfQpjdCA8LSAnQ2VsbFR5cGVfcHJlZGljdCcKCmJhcl9wbG90dGVyIDwtIGZ1bmN0aW9uKGRhdGEgPSBjdHBfbW0xMTEsIGEgPSAnbGVpZGVuJywgYiA9IGN0KXsKICBkYXRhICU+JSAKICAgIG11dGF0ZShsZWlkZW4gPSBhcy5mYWN0b3IobGVpZGVuKSkgJT4lIAogICAgZ3JvdXBfYnkoLmRhdGFbW2FdXSwgLmRhdGFbW2JdXSkgJT4lIAogICAgc3VtbWFyaXNlKENvdW50ID0gbigpKSAlPiUgCiAgICBtdXRhdGUoUmF0aW8gPSBDb3VudCAvIHN1bShDb3VudCksCiAgICAgICAgICAgYWNyb3NzKGFsbF9vZihiKSwgc3RyaW5ncjo6c3RyX3dyYXAsd2lkdGggPSAxNSkpICU+JSAKICAgIGZpbHRlcihSYXRpbyA+IDAuMDUpICU+JSAKICAgIGdncGxvdChhZXMoeD1SYXRpbyx5PS5kYXRhW1thXV0sIGZpbGwgPSAKICAgICAgICAgICAgICAgICAuZGF0YVtbYl1dLCBsYWJlbCA9IAogICAgICAgICAgICAgICAgIC5kYXRhW1tiXV0pKSArCiAgICBnZW9tX2JhcihzdGF0PSdpZGVudGl0eScpICsgCiAgICAjc2hhZG93dGV4dDo6Z2VvbV9zaGFkb3d0ZXh0KHBvc2l0aW9uID0gcG9zaXRpb25fc3RhY2sodmp1c3QgPSAwLjUpLCBjb2xvciA9ICdibGFjaycsIGJnLmNvbG91cj0nd2hpdGUnKSArCiAgICBnZ3JlcGVsOjpnZW9tX3RleHRfcmVwZWwocG9zaXRpb24gPSBwb3NpdGlvbl9zdGFjayh2anVzdCA9IDAuNSksIGJnLmNvbG9yID0gJ3doaXRlJywgZGlyZWN0aW9uID0gJ3gnKSAgKwogICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYyhwYWxzOjphbHBoYWJldDIoKSwgcGFsczo6Z2xhc2JleSgpKSAlPiUgdW5uYW1lKCkpICsgCiAgICBjb3dwbG90Ojp0aGVtZV9jb3dwbG90KCkgKwogICAgc2NhbGVfeF9jb250aW51b3VzKGxpbWl0cyA9IGMoMCwxLjEpKSArCiAgICBjb29yZF9jYXJ0ZXNpYW4oY2xpcCA9ICJvZmYiKSAKfQpiYXJfcGxvdHRlcihjdHBfbW0xMTFfc2NlaWFkLCAnbGVpZGVuJywgY3QpCmBgYAoKIyMjIyBDZWxsVHlwZSBwcmVkaWN0IGZyb20gc2NFaWFEIDIwMjUgSHVtYW4gRnVsbCBFeWUKYGBge3IsIGZpZy5oZWlnaHQ9MTUsIGZpZy53aWR0aD0xMn0KY3QgPC0gJ0NUX19zY2VpYWRfMjAyNTAxMDdfZnVsbF9tbUdlbmVGaWx0ZXInCmJhcl9wbG90dGVyKGN0cF9tbTExMV9zY2VpYWQsICdsZWlkZW4nLCBjdCkKYGBgCgoKCiMjIEFsaWdubWVudCBvZiBDZWxsIFR5cGUgQ2FsbHMKClVzaW5nOgoxLiBNUkNBIFByZWRpY3Rpb25zCjIuIHNjRWlhRCBIdW1hbiBFeWUgKG1tR2VuZUZpbHRlcikgUHJlZGljdGlvbnMKMy4gTWFqb3JDZWxsVHlwZSAoY3VyYXRlZCBhdXRob3IpCjQuIENlbGxUeXBlX3ByZWRpY3QgKHNjRWlhRCAyMDIyIHB1YmxpY2F0aW9uIG1hY2hpbmUgbGFiZWxzKQpgYGB7cn0KYmluZF9jb2xzKAogIGN0cF9tbTExMV9fbXJjYSwKICBjdHBfbW0xMTFfc2NlaWFkICU+JSBzZWxlY3QoY29udGFpbnMoJ2dlbmVGaWx0ZXInKSkKKSAlPiUgCiAgZ3JvdXBfYnkoQ1RfX3NjZWlhZF8yMDI1MDEwN19mdWxsX21tR2VuZUZpbHRlciwgTWFqb3JDZWxsVHlwZSwgQ2VsbFR5cGVfcHJlZGljdCwgQ1RfX2NoZW5fbXJjYSkgJT4lIAogIG11dGF0ZShDVF9fc2NlaWFkXzIwMjUwMTA3X2Z1bGxfbW1HZW5lRmlsdGVyID0gZ3N1YigiXFxzXFwoLip8XFxzcm9kIiwiIixDVF9fc2NlaWFkXzIwMjUwMTA3X2Z1bGxfbW1HZW5lRmlsdGVyKSwKICAgICAgICAgQ2VsbFR5cGVfcHJlZGljdCA9IHRvbG93ZXIoQ2VsbFR5cGVfcHJlZGljdCkgJT4lIGdzdWIoIlxcc2NlbGx8XFxzZ2xpYXxcXHNyb2QiLCIiLC4pLAogICAgICAgICBDVF9fY2hlbl9tcmNhID0gY2FzZV93aGVuKGdyZXBsKCJhbWFjcmluZSIsIENUX19jaGVuX21yY2EpIH4gImFtYWNyaW5lIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBncmVwbCgicmV0aW5hbCBjb25lIiwgQ1RfX2NoZW5fbXJjYSkgfiAiY29uZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JlcGwoInJldGluYWwgcm9kIiwgQ1RfX2NoZW5fbXJjYSkgfiAicm9kIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBncmVwbCgiYmlwb2xhciIsIENUX19jaGVuX21yY2EpIH4gImJpcG9sYXIiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyZXBsKCJob3Jpem9udGFsIiwgQ1RfX2NoZW5fbXJjYSkgfiAiaG9yaXpvbnRhbCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JlcGwoInJldGluYWwgcGlnbWVudCIsIENUX19jaGVuX21yY2EpIH4gInJwZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVFJVRSB+IENUX19jaGVuX21yY2EpICU+JSB0b2xvd2VyKCkgJT4lIGdzdWIoIiBjZWxsIiwiIiwuKSkgJT4lIAogIHN1bW1hcmlzZShDb3VudCA9IG4oKSkgJT4lIAogIGFycmFuZ2UoQ1RfX3NjZWlhZF8yMDI1MDEwN19mdWxsX21tR2VuZUZpbHRlciwgLUNvdW50KSAlPiUgCiAgZmlsdGVyKENvdW50ID4gNSkgJT4lIAogIERUOjpkYXRhdGFibGUoKSAKYGBgCgoKYGBge3J9CmN0X2NhbGxfY291bnRzIDwtIGJpbmRfY29scygKICBjdHBfbW0xMTFfX21yY2EsCiAgY3RwX21tMTExX3NjZWlhZCAlPiUgc2VsZWN0KGNvbnRhaW5zKCdnZW5lRmlsdGVyJykpCikgJT4lIAogIHNlbGVjdChiYXJjb2RlLCBDVF9fc2NlaWFkXzIwMjUwMTA3X2Z1bGxfbW1HZW5lRmlsdGVyLCBNYWpvckNlbGxUeXBlLCBDZWxsVHlwZV9wcmVkaWN0LCBDVF9fY2hlbl9tcmNhKSAlPiUgCiAgbXV0YXRlKENUX19zY2VpYWRfMjAyNTAxMDdfZnVsbF9tbUdlbmVGaWx0ZXIgPSBnc3ViKCJcXHNcXCguKnxcXHNyb2QiLCIiLENUX19zY2VpYWRfMjAyNTAxMDdfZnVsbF9tbUdlbmVGaWx0ZXIpLAogICAgICAgICBDZWxsVHlwZV9wcmVkaWN0ID0gdG9sb3dlcihDZWxsVHlwZV9wcmVkaWN0KSAlPiUgZ3N1YigiXFxzY2VsbHxcXHNnbGlhfFxcc3JvZCIsIiIsLiksCiAgICAgICAgIENUX19jaGVuX21yY2EgPSBjYXNlX3doZW4oZ3JlcGwoImFtYWNyaW5lIiwgQ1RfX2NoZW5fbXJjYSkgfiAiYW1hY3JpbmUiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyZXBsKCJyZXRpbmFsIGNvbmUiLCBDVF9fY2hlbl9tcmNhKSB+ICJjb25lIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBncmVwbCgicmV0aW5hbCByb2QiLCBDVF9fY2hlbl9tcmNhKSB+ICJyb2QiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyZXBsKCJiaXBvbGFyIiwgQ1RfX2NoZW5fbXJjYSkgfiAiYmlwb2xhciIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JlcGwoImhvcml6b250YWwiLCBDVF9fY2hlbl9tcmNhKSB+ICJob3Jpem9udGFsIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBncmVwbCgicmV0aW5hbCBwaWdtZW50IiwgQ1RfX2NoZW5fbXJjYSkgfiAicnBlIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUUlVFIH4gQ1RfX2NoZW5fbXJjYSkgJT4lIHRvbG93ZXIoKSAlPiUgZ3N1YigiIGNlbGwiLCIiLC4pKSAlPiUgCiAgcGl2b3RfbG9uZ2VyKC1iYXJjb2RlKSAlPiUgCiAgbXV0YXRlKHZhbHVlID0gY2FzZV93aGVuKGlzLm5hKHZhbHVlKSB+ICcnLAogICAgICAgICAgICAgICAgICAgICAgICAgICBUUlVFIH4gdmFsdWUpKSAlPiUgCiAgZ3JvdXBfYnkoYmFyY29kZSwgdmFsdWUpICU+JSAKICBzdW1tYXJpc2UoQ291bnQgPSBuKCkpCgpjb25zZW5zdXMgPC0gY3RfY2FsbF9jb3VudHMgJT4lIAogIGZpbHRlcih2YWx1ZSAhPSAnJywgQ291bnQgPiAxKSAlPiUgCiAgc2xpY2VfbWF4KG9yZGVyX2J5ID0gQ291bnQsIG4gPSAxKQoKCmBgYAoKIyMgT3V0cHV0IEJhcmNvZGVzCmBgYHtyfQpzZXQuc2VlZCgyMDI1MDE1NCkKbW0xMTFfZnVsbF9leWVfcmVmX2JjcyA8LSBjdHBfbW0xMTFfX21yY2EgJT4lIHVuZ3JvdXAoKSAlPiUgCiAgbGVmdF9qb2luKGNvbnNlbnN1cyAlPiUgc2VsZWN0KGJhcmNvZGUsIGNvbnNlbnN1cyA9IHZhbHVlLCBjb25zZW5zdXNfY291bnQgPSBDb3VudCkpICU+JSAKICBtdXRhdGUoY29uc2Vuc3VzID0gY2FzZV93aGVuKGlzLm5hKGNvbnNlbnN1cykgfiAnVW5rbm93bicsIFRSVUUgfiBjb25zZW5zdXMpKSAlPiUgCiAgZ3JvdXBfYnkoc3R1ZHlfYWNjZXNzaW9uLCBjb25zZW5zdXMpICU+JSAKICBzYW1wbGVfbig1MDAsIHJlcGxhY2UgPSBUUlVFKSAlPiUgCiAgdW5pcXVlKCkKCm1tMTExX2Z1bGxfZXllX3F1ZXJ5X2JjcyA8LSBjdHBfbW0xMTFfX21yY2EgJT4lIHVuZ3JvdXAoKSAlPiUgCiAgZmlsdGVyKCFiYXJjb2RlICVpbiUgbW0xMTFfZnVsbF9leWVfcmVmX2JjcyRiYXJjb2RlKQoKI21tMTExX2Z1bGxfZXllX3JlZl9iY3MkYmFyY29kZSAlPiUgd3JpdGUoZ3pmaWxlKCd+L2dpdC9zY0VpYURfbW9kZWxpbmcvZGF0YS9tbTExMV9tYXR1cmVfZXllX3JlZl9iY3MuZnVsbC4yMDI1MDExNS5jc3YuZ3onKSkKCiNtbTExMV9mdWxsX2V5ZV9xdWVyeV9iY3MkYmFyY29kZSAlPiUgd3JpdGUoZ3pmaWxlKCd+L2dpdC9zY0VpYURfbW9kZWxpbmcvZGF0YS9tbTExMV9tYXR1cmVfZXllX3F1ZXJ5X2Jjcy5mdWxsLjIwMjUwMTE1LmNzdi5neicpKQoKYGBgCgoKIyBTdGFnZSAzClJ1biBzY1ZJIGB+L2dpdC9zY0VpYURfbW9kZWxpbmcvU25ha2VtYWtlLndyYXBwZXIuc2hgIHBpcGVsaW5lIHdpdGggdGhlIHJlZiAvIHF1ZXJ5IGJhcmNvZGVzCgpgYGB7YmFzaCBvbi1iaW93dWxmMiwgZXZhbCA9IEZBTFNFfQpjZCAvZGF0YS9PR1ZGQl9CRy9zY0VpYUQvMjAyNF8wMl8yOC9zbmFrZW91dC9tbTExMV9tYXR1cmVfZXllX2Z1bGwKc2JhdGNoIHNuYWtlY2FsbC5zaAoKYGBgCgpgYGB7YmFzaCBsb2NhbCByc3luYyByZXN1bHRzLCBldmFsID0gRkFMU0V9IApyc3luYyAtUHJhdiBoMjovZGF0YS9PR1ZGQl9CRy9zY0VpYUQvMjAyNF8wMl8yOC9zbmFrZW91dC9tbTExMV9tYXR1cmVfZXllX2Z1bGwvbW0xMTFfbWF0dXJlX2V5ZV8yMDI1MDExM18yMDAwaHZnXzEwMGVfMjBsLm9icy5jc3YuZ3ogL1VzZXJzL21jZ2F1Z2hleWQvZGF0YS9zY0VpYURfbW9kZWxpbmcvbW0xMTFfbWF0dXJlX2V5ZV9mdWxsLwpgYGAKCmBgYHtyfQpzb3VyY2UoJ2FuYWx5c2lzX3NjcmlwdHMuUicpCgptbV9vYnMgPC0gcHVsbF9vYnMoJ34vZGF0YS9zY0VpYURfbW9kZWxpbmcvbW0xMTFfbWF0dXJlX2V5ZV9mdWxsL21tMTExX21hdHVyZV9leWVfMjAyNTAxMTRfMjAwMGh2Z18xMDBlXzIwbC5vYnMuY3N2Lmd6JywgbWFjaGluZV9sYWJlbCA9ICdNQ1Rfc2NBTlZJJykgCm1tX29icyRvYnMgPC0gbW1fb2JzJG9icyAlPiUgCiAgZHBseXI6OnJlbmFtZShiYXJjb2RlID1iYXJjb2RlaSkgJT4lIAogIGxlZnRfam9pbihzY2VpYWRfbWV0YSAlPiUgc2VsZWN0KGJhcmNvZGUgPSBCYXJjb2RlLCBDZWxsVHlwZV9wcmVkaWN0KSwgYnkgPSAnYmFyY29kZScpCmBgYAoKIyMgVU1BUCBQbG90cwpgYGB7ciwgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9MTZ9CmEgPC0gbW1fb2JzJG9icyAlPiUgCiAgbGVmdF9qb2luKGNvbnNlbnN1cyAlPiUgc2VsZWN0KGJhcmNvZGUsIGNvbnNlbnN1cyA9IHZhbHVlLCBjb25zZW5zdXNfY291bnQgPSBDb3VudCkpICU+JSAKICBncm91cF9ieShjb25zZW5zdXMpICU+JSAKICBzYW1wbGVfbig1MDAwLCByZXBsYWNlID0gVFJVRSkgJT4lIAogIHVuaXF1ZSgpICU+JSAKICBnZ3Bsb3QoYWVzKHg9dW1hcDEseT11bWFwMikpICsKICBzY2F0dGVybW9yZTo6Z2VvbV9zY2F0dGVybW9yZShhZXMoY29sb3IgPSBjb25zZW5zdXMpLCBwb2ludHNpemUgPSAwLjgsIGFscGhhID0gMC41KSArCiAgZ2dyZXBlbDo6Z2VvbV9sYWJlbF9yZXBlbChkYXRhID0gLiAlPiUgZ3JvdXBfYnkoY29uc2Vuc3VzKSAlPiUgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN1bW1hcmlzZSh1bWFwMSA9IG1lZGlhbih1bWFwMSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB1bWFwMiA9IG1lZGlhbih1bWFwMikpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgYWVzKGxhYmVsID0gY29uc2Vuc3VzLCBjb2xvciA9IGNvbnNlbnN1cykpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYyhwYWxzOjphbHBoYWJldDIoKSwgcGFsczo6Z2xhc2JleSgpKSAlPiUgdW5uYW1lKCkpICsgCiAgY293cGxvdDo6dGhlbWVfY293cGxvdCgpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArCiAgZ2d0aXRsZSgiY29uc2Vuc3VzIikKCmIgPC0gbW1fb2JzJG9icyAlPiUgCiAgbGVmdF9qb2luKG1tX29icyRsYWJlbHMsIGJ5ID0gJ2xlaWRlbjMnKSAlPiUgCiAgZ2dwbG90KGFlcyh4PXVtYXAxLHk9dW1hcDIpKSArCiAgc2NhdHRlcm1vcmU6Omdlb21fc2NhdHRlcm1vcmUoYWVzKGNvbG9yID0gTWFqb3JDZWxsVHlwZSksIHBvaW50c2l6ZSA9IDAuOCwgYWxwaGEgPSAwLjUpICsKICBnZ3JlcGVsOjpnZW9tX2xhYmVsX3JlcGVsKGRhdGEgPSAuICU+JSBncm91cF9ieShNYWpvckNlbGxUeXBlKSAlPiUgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN1bW1hcmlzZSh1bWFwMSA9IG1lZGlhbih1bWFwMSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB1bWFwMiA9IG1lZGlhbih1bWFwMikpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgYWVzKGxhYmVsID0gTWFqb3JDZWxsVHlwZSwgY29sb3IgPSBNYWpvckNlbGxUeXBlKSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKHBhbHM6OmFscGhhYmV0MigpLCBwYWxzOjpnbGFzYmV5KCkpICU+JSB1bm5hbWUoKSkgKyAKICBjb3dwbG90Ojp0aGVtZV9jb3dwbG90KCkgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsKICBnZ3RpdGxlKCJNYWpvckNlbGxUeXBlIikKCmNvd3Bsb3Q6OnBsb3RfZ3JpZChhLGIsbnJvdyA9IDEpCgpgYGAKCmBgYHtyLCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD0yMH0KCmMgPC0gbW1fb2JzJG9icyAlPiUgCiAgbGVmdF9qb2luKGNvbnNlbnN1cyAlPiUgc2VsZWN0KGJhcmNvZGUsIGNvbnNlbnN1cyA9IHZhbHVlLCBjb25zZW5zdXNfY291bnQgPSBDb3VudCkpICU+JSAKICBncm91cF9ieShjb25zZW5zdXMpICU+JSAKICBzYW1wbGVfbig1MDAwLCByZXBsYWNlID0gVFJVRSkgJT4lIAogIHVuaXF1ZSgpICU+JSAKICBnZ3Bsb3QoYWVzKHg9dW1hcDEseT11bWFwMikpICsKICBzY2F0dGVybW9yZTo6Z2VvbV9zY2F0dGVybW9yZShhZXMoY29sb3IgPSBjb25zZW5zdXMpLCBwb2ludHNpemUgPSA0LjgsIGFscGhhID0gMC41KSArCiAgZmFjZXRfd3JhcCh+c3R1ZHlfYWNjZXNzaW9uKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMocGFsczo6YWxwaGFiZXQyKCksIHBhbHM6OmdsYXNiZXkoKSkgJT4lIHVubmFtZSgpKSArIAogIGNvd3Bsb3Q6OnRoZW1lX2Nvd3Bsb3QoKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKwogIGdndGl0bGUoImNvbG9yIGJ5IGNvbnNlbnN1cywgc3BsaXQgYnkgc3R1ZHkiKQoKCgpkIDwtIG1tX29icyRvYnMgJT4lIAogIGxlZnRfam9pbihjb25zZW5zdXMgJT4lIHNlbGVjdChiYXJjb2RlLCBjb25zZW5zdXMgPSB2YWx1ZSwgY29uc2Vuc3VzX2NvdW50ID0gQ291bnQpKSAlPiUgCiAgZ3JvdXBfYnkoY29uc2Vuc3VzKSAlPiUgCiAgc2FtcGxlX24oNTAwMCwgcmVwbGFjZSA9IFRSVUUpICU+JSAKICB1bmlxdWUoKSAlPiUgCiAgZ2dwbG90KGFlcyh4PXVtYXAxLHk9dW1hcDIpKSArCiAgc2NhdHRlcm1vcmU6Omdlb21fc2NhdHRlcm1vcmUoYWVzKGNvbG9yID0gc3R1ZHlfYWNjZXNzaW9uKSxwb2ludHNpemUgPSA0LjgsIGFscGhhID0gMSkgKwogIGZhY2V0X3dyYXAofmNvbnNlbnN1cykgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKHBhbHM6OmFscGhhYmV0MigpLCBwYWxzOjpnbGFzYmV5KCkpICU+JSB1bm5hbWUoKSkgKyAKICBjb3dwbG90Ojp0aGVtZV9jb3dwbG90KCkgKwogIGdndGl0bGUoImNvbG9yIGJ5IHN0dWR5LCBzcGxpdCBieSBjb25zZW5zdXMiKQoKY293cGxvdDo6cGxvdF9ncmlkKGMsZCxucm93PTEpCmBgYAojIyBMb29rcyBHb29kISBOb3cgbGV0IGRvIHNvbWUgdHJpbW1pbmcgYWNjb3JkaW5nIHRvIGxlaWRlbiBjbHVzdGVyaW5nCgojIyMgcHNldWRvYnVsayAobGVpZGVuMykgdHJlZQpHb2FsOiByZW1vdmUgd2Fja3kgbG9va2luZyBjbHVzdGVycwpgYGB7ciwgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTE1fQpsaWJyYXJ5KGdndHJlZSkKcGIgPC0gZGF0YS50YWJsZTo6ZnJlYWQoJ34vZGF0YS9zY0VpYURfbW9kZWxpbmcvbW0xMTFfbWF0dXJlX2V5ZV9mdWxsL21tMTExX21hdHVyZV9leWVfMjAyNTAxMTRfMjAwMGh2Z18xMDBlXzIwbC5wc2V1ZG9CdWxrLmxlaWRlbjMuY3N2Lmd6JykKY29sbmFtZXMocGIpIDwtIGdzdWIoIlxcLlxcZCsiLCIiLGNvbG5hbWVzKHBiKSkKaHZnIDwtIGRhdGEudGFibGU6OmZyZWFkKCd+L2RhdGEvc2NFaWFEX21vZGVsaW5nL21tMTExX21hdHVyZV9leWVfZnVsbC9odmcuMjAwMC5jc3YuZ3onKVstMSxdCnJuYW1lcyA8LSBwYiRWMQpjbHVzdCA8LSBzdHJfZXh0cmFjdChybmFtZXMsICdcXGQrJykgJT4lIGFzLmludGVnZXIoKQpwYiA8LSBwYlssLTFdICU+JSBhcy5tYXRyaXgoKQpyb3cubmFtZXMocGIpIDwtIGFzLmNoYXJhY3RlcihjbHVzdCkKCnBiX25vcm0gPC0gbWV0YW1vUnBoOjpub3JtYWxpemVfZGF0YSh0KHBiKSwgc2FtcGxlX3NjYWxlID0gJ2NwbScpICU+JSB0KCkgCgpwYl9ub3JtIDwtIHBiX25vcm1bLGh2ZyRWMl0KI3BiX25vcm0gPC0gcGJfbm9ybVssaHZnJFYyWyFodmckVjIgJWluJSBjKGNjX2dlbmVzLHJpYm9fZ2VuZXMpXV0KIyBodHRwczovL3N0YXRzLnN0YWNrZXhjaGFuZ2UuY29tL3F1ZXN0aW9ucy8zMTU2NS9jb21wdXRlLWEtY29zaW5lLWRpc3NpbWlsYXJpdHktbWF0cml4LWluLXIKc2ltIDwtIHBiX25vcm0gLyBzcXJ0KHJvd1N1bXMocGJfbm9ybSAqIHBiX25vcm0pKQpzaW0gPC0gc2ltICUqJSB0KHNpbSkKRF9zaW0gPC0gYXMuZGlzdCgxIC0gc2ltKQoKaGNsdXN0X3NpbSA8LSBoY2x1c3QoRF9zaW0sIG1ldGhvZCA9ICdhdmVyYWdlJykKCnBiX2xhYmVscyA8LSBtbV9vYnMkbGFiZWxzICU+JSBtdXRhdGUobGVpZGVuMz1hcy5jaGFyYWN0ZXIobGVpZGVuMykpCgojIG1hY2hpbmUgY2FsbHMKcCA8LSBnZ3RyZWUoaGNsdXN0X3NpbSkKcCRkYXRhIDwtIHAkZGF0YSAlPiUgbGVmdF9qb2luKHBiX2xhYmVscywgYnkgPSBjKCJsYWJlbCIgPSAibGVpZGVuMyIpKSAKZiA8LSBwICsgbGF5b3V0X2RlbmRyb2dyYW0oKSArCiAgZ2VvbV90aXBsYWIoYWVzKGxhYmVsID0gcGFzdGUobGFiZWwsIG1NQ1QsIHN0dWR5Q291bnQsIFRvdGFsQ291bnQsIHNlcCA9ICcgLSAnKSwgY29sb3IgPSBtQ1QpKSArIAogIHRoZW1lX2RlbmRyb2dyYW0ocGxvdC5tYXJnaW49bWFyZ2luKDE2LDE2LDMwMCwxNikpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYyhwYWxzOjphbHBoYWJldDIoKSwgcGFsczo6Z2xhc2JleSgpKSAlPiUgdW5uYW1lKCkpICsgCiAgZ3VpZGVzKGNvbG9yPSJub25lIikKCiMgY29uc2Vuc3VzIGNhbGxzCnBiX2xhYmVscyA8LSBtbV9vYnMkb2JzICU+JSAKICBsZWZ0X2pvaW4oY29uc2Vuc3VzICU+JSBzZWxlY3QoYmFyY29kZSwgY29uc2Vuc3VzID0gdmFsdWUsIGNvbnNlbnN1c19jb3VudCA9IENvdW50KSwgYnkgPSAnYmFyY29kZScpICU+JSAKICBncm91cF9ieShsZWlkZW4zLCBjb25zZW5zdXMpICU+JSAKICBzdW1tYXJpc2UoQ291bnQgPSBuKCkpICU+JSAKICBtdXRhdGUoUmF0aW8gPSBDb3VudCAvIHN1bShDb3VudCkpICU+JSAKICBmaWx0ZXIoUmF0aW8gPiAwLjEpICU+JSAKICBhcnJhbmdlKC1SYXRpbykgJT4lIAogIG11dGF0ZShjcGVyYyA9IHBhc3RlMChjb25zZW5zdXMsICIgKCIsIHJvdW5kKFJhdGlvLDIpLCAiKSIpKSAlPiUgCiAgc3VtbWFyaXNlKENUcyA9IHBhc3RlMChjcGVyYywgY29sbGFwc2UgPSAnLCAnKSkgJT4lIAogIG11dGF0ZShsZWlkZW4zID0gYXMuY2hhcmFjdGVyKGxlaWRlbjMpLAogICAgICAgICBDVG0gPSBnc3ViKCIgXFwoLioiLCIiLENUcykpCgpwIDwtIGdndHJlZShoY2x1c3Rfc2ltKQpwJGRhdGEgPC0gcCRkYXRhICU+JSBsZWZ0X2pvaW4ocGJfbGFiZWxzLCBieSA9IGMoImxhYmVsIiA9ICJsZWlkZW4zIikpIApnIDwtIHAgKyBsYXlvdXRfZGVuZHJvZ3JhbSgpICsKICBnZW9tX3RpcGxhYihhZXMobGFiZWwgPSBwYXN0ZShsYWJlbCxDVHMsIHNlcCA9ICcgLSAnKSwgY29sb3IgPSBDVG0pKSArIAogIHRoZW1lX2RlbmRyb2dyYW0ocGxvdC5tYXJnaW49bWFyZ2luKDE2LDE2LDMwMCwxNikpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYyhwYWxzOjphbHBoYWJldDIoKSwgcGFsczo6Z2xhc2JleSgpKSAlPiUgdW5uYW1lKCkpICsgCiAgZ3VpZGVzKGNvbG9yPSJub25lIikKCmggPC0gbW1fb2JzJG9icyAlPiUgbXV0YXRlKGxlaWRlbjM9IGFzLmZhY3RvcihsZWlkZW4zKSkgJT4lIAogIGdncGxvdChhZXMoeD11bWFwMSx5PXVtYXAyKSkgKwogIHNjYXR0ZXJtb3JlOjpnZW9tX3NjYXR0ZXJtb3JlKGFlcyhjb2xvciA9IGxlaWRlbjMpLCBwb2ludHNpemUgPSAwLjgsIGFscGhhID0gMC41KSArCiAgZ2dyZXBlbDo6Z2VvbV90ZXh0X3JlcGVsKGRhdGEgPSAuICU+JSBncm91cF9ieShsZWlkZW4zKSAlPiUgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3VtbWFyaXNlKHVtYXAxID0gbWVkaWFuKHVtYXAxKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdW1hcDIgPSBtZWRpYW4odW1hcDIpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgYWVzKGxhYmVsID0gbGVpZGVuMywgY29sb3IgPSBsZWlkZW4zKSwgYmcuY29sb3IgPSAid2hpdGUiKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMocGFsczo6YWxwaGFiZXQyKCksIHBhbHM6OmdsYXNiZXkoKSwgcGFsczo6a2VsbHkoKSwgcGFsczo6YWxwaGFiZXQoKSwgcGFsczo6YnJld2VyLnNldDEobj0xMCkpICU+JSB1bm5hbWUoKSkgKyAKICBjb3dwbG90Ojp0aGVtZV9jb3dwbG90KCkgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsKICBnZ3RpdGxlKCJjb25zZW5zdXMiKQoKY293cGxvdDo6cGxvdF9ncmlkKGNvd3Bsb3Q6OnBsb3RfZ3JpZChmLGcsIG5yb3cgPTIpLAogICAgICAgICAgICAgICAgICAgY293cGxvdDo6cGxvdF9ncmlkKGEsaCwgbnJvdyA9IDEpLAogICAgICAgICAgICAgICAgICAgbnJvdyA9IDIsIHJlbF9oZWlnaHRzID0gYygxLDAuNCkpCmBgYAoKYGBge3J9CnJlbW92ZV9sZWlkZW4zIDwtIGMoMTA1LAogICAgICAgICAgICAgICAgICAgIDk3LAogICAgICAgICAgICAgICAgICAgIDEwMSwKICAgICAgICAgICAgICAgICAgICA3MSwKICAgICAgICAgICAgICAgICAgICAxNiwKICAgICAgICAgICAgICAgICAgICA2LAogICAgICAgICAgICAgICAgICAgIDg3LAogICAgICAgICAgICAgICAgICAgIDk5LAogICAgICAgICAgICAgICAgICAgIDczLCAzOSwgNjAsIDgyKQoKbW1fb2JzJG9icyAlPiUgCiAgbGVmdF9qb2luKGNvbnNlbnN1cyAlPiUgc2VsZWN0KGJhcmNvZGUsIGNvbnNlbnN1cyA9IHZhbHVlLCBjb25zZW5zdXNfY291bnQgPSBDb3VudCkpICU+JSAKICBncm91cF9ieShjb25zZW5zdXMpICU+JSAKICBzYW1wbGVfbig1MDAwLCByZXBsYWNlID0gVFJVRSkgJT4lIAogIHVuaXF1ZSgpICU+JSAKICBtdXRhdGUocmVtb3ZlID0gY2FzZV93aGVuKGxlaWRlbjMgJWluJSByZW1vdmVfbGVpZGVuMyB+ICdyZW1vdmUnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgVFJVRSB+ICdyZXRhaW4nKSkgJT4lIAogIGdncGxvdChhZXMoeD11bWFwMSx5PXVtYXAyKSkgKwogIHNjYXR0ZXJtb3JlOjpnZW9tX3NjYXR0ZXJtb3JlKGFlcyhjb2xvciA9IGNvbnNlbnN1cyksIHBvaW50c2l6ZSA9IDMuOCwgYWxwaGEgPSAwLjUpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYyhwYWxzOjphbHBoYWJldDIoKSwgcGFsczo6Z2xhc2JleSgpKSAlPiUgdW5uYW1lKCkpICsgCiAgY293cGxvdDo6dGhlbWVfY293cGxvdCgpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArCiAgZ2d0aXRsZSgiY29uc2Vuc3VzIikgKwogIGZhY2V0X3dyYXAofnJlbW92ZSkKYGBgCgojIyMgRWFzeSBhbmQgTWVkaXVtIENhbGxzCkVhc3kgaXMgYW55IGNsdXN0ZXIgd2l0aCA+OTAlIGNvbnNlbnN1cwoKTWVkaXVtIGlzIGFueSBjbHVzdGVyIHRoYXQgSSBjYW4gZWFzaWx5IGhhbmQgdmVyaWZ5IGFzID45MCUgKG9mdGVuIHlvdSBnZXQgY29tYm9zIHRoYXQgYXJlIGVmZmVjdGl2ZWx5IHRoZSBzYW1lKS4KYGBge3J9CmVhc3lfY2FsbHMgPC0gbW1fb2JzJG9icyAlPiUgCiAgZmlsdGVyKCFsZWlkZW4zICVpbiUgcmVtb3ZlX2xlaWRlbjMpICU+JSAKICBsZWZ0X2pvaW4oY29uc2Vuc3VzICU+JSBzZWxlY3QoYmFyY29kZSwgY29uc2Vuc3VzID0gdmFsdWUsIGNvbnNlbnN1c19jb3VudCA9IENvdW50KSwgYnkgPSAnYmFyY29kZScpICU+JSAKICBncm91cF9ieShsZWlkZW4zLCBjb25zZW5zdXMpICU+JSAKICBzdW1tYXJpc2UoQ291bnQgPSBuKCkpICU+JSAKICBtdXRhdGUoUmF0aW8gPSBDb3VudCAvIHN1bShDb3VudCkpICU+JSAKICBmaWx0ZXIoUmF0aW8gPiAwLjksICFpcy5uYShjb25zZW5zdXMpKQoKcmVtYWluZGVyXzAxIDwtIG1tX29icyRvYnMgJT4lIGZpbHRlcighbGVpZGVuMyAlaW4lIGVhc3lfY2FsbHMkbGVpZGVuMykgJT4lIAogIGZpbHRlcighbGVpZGVuMyAlaW4lIHJlbW92ZV9sZWlkZW4zKSAlPiUgCiAgbGVmdF9qb2luKGN0cF9tbTExMV9fbXJjYSAlPiUgc2VsZWN0KGJhcmNvZGUsQ1RfX2NoZW5fbXJjYSksIGJ5ID0gJ2JhcmNvZGUnKSAlPiUgCiAgbGVmdF9qb2luKGN0cF9tbTExMV9zY2VpYWQgJT4lIHNlbGVjdChiYXJjb2RlLENUX19zY2VpYWRfMjAyNTAxMDdfZnVsbF9tbUdlbmVGaWx0ZXIsIENUX19zYW5lc19jb21wbGV0ZV9vY3VsYXJfYXRsYXNfU0NQMjMxMCksIGJ5ID0gJ2JhcmNvZGUnKSAKCnJlbWFpbmRlcl8wMSAlPiUgCiAgZ3JvdXBfYnkobGVpZGVuMywgTWFqb3JDZWxsVHlwZSwgQ1RfX3NjZWlhZF8yMDI1MDEwN19mdWxsX21tR2VuZUZpbHRlciwgQ1RfX2NoZW5fbXJjYSkgJT4lIAogIHN1bW1hcmlzZShDb3VudCA9bigpKSAlPiUgCiAgbGVmdF9qb2luKHJlbWFpbmRlcl8wMSAlPiUgZ3JvdXBfYnkobGVpZGVuMykgJT4lIHN1bW1hcmlzZShsQ291bnQgPSBuKCkpLCBieSA9J2xlaWRlbjMnKSAlPiUgCiAgbXV0YXRlKFJhdGlvID0gQ291bnQvbENvdW50KSAlPiUgZmlsdGVyKFJhdGlvID4gMC4wNSkgJT4lIAogIERUOjpkYXRhdGFibGUoKQoKY3RzIDwtIGxpc3QoKQpjdHMkY29uZSA8LSBjKDE4LCAyOCwgOTQpCmN0cyRhbWFjcmluZSA8LSBjKDE0LCAyMywgMzgpIApjdHMkYGJpcG9sYXIgKHJvZClgIDwtIGMoNSkKY3RzJG11ZWxsZXIgPC0gYyg0NSkKY3RzJG1pY3JvZ2xpYSA8LSBjKDc0LCA4MiwgODMpCmN0cyRtb25vY3l0ZSA8LSBjKDg4KQpjdHMkYHJldGluYWwgZ2FuZ2xpb25gIDwtIGMoNywgNzApCmN0cyRwZXJpY3l0ZSA8LSBjKDM0KQpjdHMkYXN0cm9jeXRlIDwtIGMoODEpCgpgYGAKCiMjIyBIYXJkKGVyKSBDYWxscwoKTW9zdGx5IHJhcmVyIGZyb250IGV5ZS4gQWRkaW5nIGluIHRoZSBTYW5lcyAiQ29tcGxldGUgT2N1bGFyIEF0bGFzIiBwcmVkaWN0aW9ucwpgYGB7cn0KcmVtYWluZGVyXzAyIDwtIHJlbWFpbmRlcl8wMSAlPiUgZmlsdGVyKCFsZWlkZW4zICVpbiUgKGN0cyAlPiUgdW5saXN0KCkpKQoKcmVtYWluZGVyXzAyICU+JSAKICBncm91cF9ieShsZWlkZW4sIGxlaWRlbjIpICU+JSAKICBzdW1tYXJpc2UoQ291bnQgPSBuKCkpICU+JSAKICBtdXRhdGUoUmF0aW8gPSBDb3VudC9zdW0oQ291bnQpKSAlPiUgCiAgZmlsdGVyKFJhdGlvID4gMC4xKQoKCnJlbWFpbmRlcl8wMiAlPiUgCiAgZ3JvdXBfYnkobGVpZGVuMywgTWFqb3JDZWxsVHlwZSwgQ1RfX3NhbmVzX2NvbXBsZXRlX29jdWxhcl9hdGxhc19TQ1AyMzEwLCBDVF9fc2NlaWFkXzIwMjUwMTA3X2Z1bGxfbW1HZW5lRmlsdGVyLCBDVF9fY2hlbl9tcmNhKSAlPiUgCiAgc3VtbWFyaXNlKENvdW50ID1uKCkpICU+JSAKICBsZWZ0X2pvaW4ocmVtYWluZGVyXzAyICU+JSBncm91cF9ieShsZWlkZW4zKSAlPiUgc3VtbWFyaXNlKGxDb3VudCA9IG4oKSksIGJ5ID0nbGVpZGVuMycpICU+JSAKICBtdXRhdGUoUmF0aW8gPSBDb3VudC9sQ291bnQpICU+JSBmaWx0ZXIoUmF0aW8gPiAwLjA1KSAlPiUgCiAgRFQ6OmRhdGF0YWJsZSgpCgpjdHMkZXBpdGhlbGlhbCA8LSBjKDEwLDI5LCA3NywgOTEpCmN0cyRmaWJyb2JsYXN0IDwtIGMoMzEsIDkyKQpjdHMkYGNpbGlhcnkgYm9keWAgPC0gYyg4OSkKY3RzJGB0L25rYCA8LSBjKDk2KQpjdHMkbWFzdCA8LSBjKDEwNCkKYGBgCgojIyBUdW5lZCBDVCBDYWxscwpgYGB7cn0KdHVuZWRfY2FsbHMgPC0gYmluZF9yb3dzKGVhc3lfY2FsbHMgJT4lIGRwbHlyOjpyZW5hbWUoQ1Q9Y29uc2Vuc3VzKSwgY3RzICU+JSBlbmZyYW1lKG5hbWUgPSAnQ1QnLCB2YWx1ZSA9ICdsZWlkZW4zJykgJT4lIHVubmVzdCgpKQptbV9vYnMkb2JzICU+JSAKICBmaWx0ZXIoIWxlaWRlbjMgJWluJSByZW1vdmVfbGVpZGVuMykgJT4lIAogIGxlZnRfam9pbih0dW5lZF9jYWxscywgYnkgPSAnbGVpZGVuMycpICU+JSAKICBncm91cF9ieShDVCkgJT4lIAogIHNhbXBsZV9uKDUwMDAsIHJlcGxhY2UgPSBUUlVFKSAlPiUgCiAgdW5pcXVlKCkgJT4lIAogIGdncGxvdChhZXMoeD11bWFwMSx5PXVtYXAyKSkgKwogIHNjYXR0ZXJtb3JlOjpnZW9tX3NjYXR0ZXJtb3JlKGFlcyhjb2xvciA9IENUKSwgcG9pbnRzaXplID0gMS4yLCBhbHBoYSA9IDAuNSkgKwogIGdncmVwZWw6Omdlb21fbGFiZWxfcmVwZWwoZGF0YSA9IC4gJT4lIGdyb3VwX2J5KENUKSAlPiUgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN1bW1hcmlzZSh1bWFwMSA9IG1lZGlhbih1bWFwMSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB1bWFwMiA9IG1lZGlhbih1bWFwMikpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgYWVzKGxhYmVsID0gQ1QsIGNvbG9yID0gQ1QpKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMocGFsczo6YWxwaGFiZXQyKCksIHBhbHM6OmdsYXNiZXkoKSkgJT4lIHVubmFtZSgpKSArIAogIGNvd3Bsb3Q6OnRoZW1lX2Nvd3Bsb3QoKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKwogIGdndGl0bGUoIlR1bmVkIENUIikKYGBgCgojIyBIUkNUCgojIyMgUGhvdG9yZWNlcHRvcnMKYGBge3J9CmRpZmZfbW0gPC0gZGF0YS50YWJsZTo6ZnJlYWQoIi9Vc2Vycy9tY2dhdWdoZXlkL2dpdC9zY0VpYURfbW9kZWxpbmcvZGF0YS9tbTExMV9tYXR1cmVfZXllXzIwMjUwMTE0XzIwMDBodmdfMTAwZV8yMGwuZGlmZnRlc3RpbmcubGVpZGVuMy5jc3YuZ3oiKQpjb252X3RhYmxlIDwtIEFubm90YXRpb25EYmk6OnNlbGVjdChvcmcuTW0uZWcuZGI6Om9yZy5NbS5lZy5kYiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAga2V5cz1nc3ViKCdcXC5cXGQrJywnJyx1bmlxdWUoZGlmZl9tbSRuYW1lcykpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2x1bW5zPWMoIkVOU0VNQkwiLCJTWU1CT0wiLCAiR0VORU5BTUUiLCAiRU5UUkVaSUQiKSwga2V5dHlwZT0iRU5TRU1CTCIpCgp0aWIgPC0gZGlmZl9tbSAlPiUgCiAgbGVmdF9qb2luKHR1bmVkX2NhbGxzLCBieSA9IGMoImJhc2UiID0gJ2xlaWRlbjMnKSkgJT4lICAKICBmaWx0ZXIoQ1QgJWluJSBjKCJjb25lIiwicm9kIikpICU+JSAKICBtdXRhdGUoRU5TRU1CTCA9IGdzdWIoIlxcLlxcZCsiLCIiLG5hbWVzKSkgJT4lIAogIGxlZnRfam9pbihjb252X3RhYmxlKSAlPiUgCiAgZmlsdGVyKFNZTUJPTCAlaW4lIHN0cl90b190aXRsZShjKCdBUlIzJywnT1BOMUxXJywnT1BOMVNXJywnUkhPJywgJ09QTjFNVycsICdSQ1ZSTicsIkNSWCIsIlBST00xIikpKSAlPiUgCiAgc2VsZWN0KFNZTUJPTCwgYmFzZSwgbG9nZm9sZGNoYW5nZXMpICU+JSAKICBwaXZvdF93aWRlcih2YWx1ZXNfZnJvbSA9IGxvZ2ZvbGRjaGFuZ2VzLCBuYW1lc19mcm9tID0gYmFzZSkKCm1hdCA8LSB0aWIgJT4lIHNlbGVjdCgtMSkgJT4lIGFzLm1hdHJpeCgpCnJvdy5uYW1lcyhtYXQpIDwtIHRpYiAlPiUgcHVsbCgxKQoKY29sX2Z1biA9IGNpcmNsaXplOjpjb2xvclJhbXAyKGMoLTUsIDAsIDUpLCBjKCJibHVlIiwgIndoaXRlIiwgInJlZCIpKQpDb21wbGV4SGVhdG1hcDo6SGVhdG1hcChtYXQsIGNvbD1jb2xfZnVuKQpocmN0IDwtIGxpc3QoKQpocmN0JGBjb25lIChzKWAgPC0gYygxOCkKaHJjdCRgY29uZSAobWwpYCA8LSBjKDI4LDY2LDMwLDk0KQoKbW1fb2JzJG9icyAlPiUgCiAgbGVmdF9qb2luKGhyY3QgJT4lIGVuZnJhbWUobmFtZSA9ICdDVCcsIHZhbHVlID0gJ2xlaWRlbjMnKSAlPiUgdW5uZXN0KCksIGJ5ID0nbGVpZGVuMycpICU+JSAKICBmaWx0ZXIoIWlzLm5hKENUKSkgJT4lIAogIGdyb3VwX2J5KENUKSAlPiUgCiAgc2FtcGxlX24oNTAwMCwgcmVwbGFjZSA9IFRSVUUpICU+JSAKICB1bmlxdWUoKSAlPiUgCiAgZ2dwbG90KGFlcyh4PXVtYXAxLHk9dW1hcDIpKSArCiAgc2NhdHRlcm1vcmU6Omdlb21fc2NhdHRlcm1vcmUoYWVzKGNvbG9yID0gQ1QpLCBwb2ludHNpemUgPSAwLjgsIGFscGhhID0gMC41KSArCiAgZ2dyZXBlbDo6Z2VvbV9sYWJlbF9yZXBlbChkYXRhID0gLiAlPiUgZ3JvdXBfYnkoQ1QpICU+JSAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3VtbWFyaXNlKHVtYXAxID0gbWVkaWFuKHVtYXAxKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHVtYXAyID0gbWVkaWFuKHVtYXAyKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBhZXMobGFiZWwgPSBDVCwgY29sb3IgPSBDVCkpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYyhwYWxzOjphbHBoYWJldDIoKSwgcGFsczo6Z2xhc2JleSgpKSAlPiUgdW5uYW1lKCkpICsgCiAgY293cGxvdDo6dGhlbWVfY293cGxvdCgpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArCiAgZ2d0aXRsZSgiUGhvdG9yZWNlcHRvcnMiKQoKYGBgCgojIyMgQmlwb2xhcgpgYGB7cn0KdGliIDwtIGRpZmZfbW0gJT4lIAogIGxlZnRfam9pbih0dW5lZF9jYWxscywgYnkgPSBjKCJiYXNlIiA9ICdsZWlkZW4zJykpICU+JSAgCiAgZmlsdGVyKGdyZXBsKCJiaXBvbGFyIiwgQ1QpKSAlPiUgCiAgbXV0YXRlKEVOU0VNQkwgPSBnc3ViKCJcXC5cXGQrIiwiIixuYW1lcykpICU+JSAKICBsZWZ0X2pvaW4oY29udl90YWJsZSkgJT4lIAogIGZpbHRlcihTWU1CT0wgJWluJSBzdHJfdG9fdGl0bGUoYygnUFJLQ0EnLCdHUk02JywnR1JJSzEnLCdOSUYzTDEnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnTElOQzAwNDcwJywnRE9LNScsJ05FTEwyJywnU1RYMTgnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnT0RGMkwnLCdGQU0xOUE0JywnTUVJUzInLCdDQUxCMScsICdGVVQ0JywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ1NDRzInLCdMUlBQUkMnLCdGRVpGMScgKSkpICU+JSAKICBzZWxlY3QoU1lNQk9MLCBiYXNlLCBsb2dmb2xkY2hhbmdlcykgJT4lIAogIHBpdm90X3dpZGVyKHZhbHVlc19mcm9tID0gbG9nZm9sZGNoYW5nZXMsIG5hbWVzX2Zyb20gPSBiYXNlKQoKbWF0IDwtIHRpYiAlPiUgc2VsZWN0KC0xKSAlPiUgYXMubWF0cml4KCkKcm93Lm5hbWVzKG1hdCkgPC0gdGliICU+JSBwdWxsKDEpCgpjb2xfZnVuID0gY2lyY2xpemU6OmNvbG9yUmFtcDIoYygtNSwgMCwgNSksIGMoImJsdWUiLCAid2hpdGUiLCAicmVkIikpCkNvbXBsZXhIZWF0bWFwOjpIZWF0bWFwKG1hdCwgY29sPWNvbF9mdW4pCgpocmN0JGBiaXBvbGFyIChyb2QpYCA8LSBjKDUsMjYpCmhyY3QkYGJpcG9sYXIgKG9mZilgIDwtIGMoMTksMzIsNDYsNTIpCmhyY3QkYGJpcG9sYXIgKG9uKWA8LSB0dW5lZF9jYWxscyAlPiUgZmlsdGVyKGdyZXBsKCJiaXBvbGFyIixDVCkpICU+JSBwdWxsKGxlaWRlbjMpCmhyY3QkYGJpcG9sYXIgKG9uKWAgPC0gaHJjdCRgYmlwb2xhciAob24pYFshaHJjdCRgYmlwb2xhciAob24pYCAlaW4lIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjKGhyY3QkYGJpcG9sYXIgKHJvZClgLCBocmN0JGBiaXBvbGFyIChvZmYpYCldCgptbV9vYnMkb2JzICU+JSAKICBsZWZ0X2pvaW4oaHJjdCAlPiUgZW5mcmFtZShuYW1lID0gJ0NUJywgdmFsdWUgPSAnbGVpZGVuMycpICU+JSB1bm5lc3QoKSwgYnkgPSdsZWlkZW4zJykgJT4lIAogIGZpbHRlcihncmVwbCgiYmlwb2xhciIsIENUKSkgJT4lIAogIGdyb3VwX2J5KENUKSAlPiUgCiAgc2FtcGxlX24oNTAwMCwgcmVwbGFjZSA9IFRSVUUpICU+JSAKICB1bmlxdWUoKSAlPiUgCiAgZ2dwbG90KGFlcyh4PXVtYXAxLHk9dW1hcDIpKSArCiAgc2NhdHRlcm1vcmU6Omdlb21fc2NhdHRlcm1vcmUoYWVzKGNvbG9yID0gQ1QpLCBwb2ludHNpemUgPSAwLjgsIGFscGhhID0gMC41KSArCiAgZ2dyZXBlbDo6Z2VvbV9sYWJlbF9yZXBlbChkYXRhID0gLiAlPiUgZ3JvdXBfYnkoQ1QpICU+JSAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3VtbWFyaXNlKHVtYXAxID0gbWVkaWFuKHVtYXAxKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHVtYXAyID0gbWVkaWFuKHVtYXAyKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBhZXMobGFiZWwgPSBDVCwgY29sb3IgPSBDVCkpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYyhwYWxzOjphbHBoYWJldDIoKSwgcGFsczo6Z2xhc2JleSgpKSAlPiUgdW5uYW1lKCkpICsgCiAgY293cGxvdDo6dGhlbWVfY293cGxvdCgpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArCiAgZ2d0aXRsZSgiQmlwb2xhciIpCmBgYAoKCiMjIyBBbWFjcmluZQpgYGB7cn0KdGliIDwtIGRpZmZfbW0gJT4lIAogIGxlZnRfam9pbih0dW5lZF9jYWxscywgYnkgPSBjKCJiYXNlIiA9ICdsZWlkZW4zJykpICU+JSAgCiAgZmlsdGVyKGdyZXBsKCJhbWFjcmluZSIsIENUKSkgJT4lIAogIG11dGF0ZShFTlNFTUJMID0gZ3N1YigiXFwuXFxkKyIsIiIsbmFtZXMpKSAlPiUgCiAgbGVmdF9qb2luKGNvbnZfdGFibGUpICU+JSAKICBmaWx0ZXIoU1lNQk9MICVpbiUgc3RyX3RvX3RpdGxlKGMoJ0dBRDEnLCdHQUQyJywnU0xDNkE5JywnTkZJQScpKSkgJT4lIAogIHNlbGVjdChTWU1CT0wsIGJhc2UsIGxvZ2ZvbGRjaGFuZ2VzKSAlPiUgCiAgcGl2b3Rfd2lkZXIodmFsdWVzX2Zyb20gPSBsb2dmb2xkY2hhbmdlcywgbmFtZXNfZnJvbSA9IGJhc2UpCgptYXQgPC0gdGliICU+JSBzZWxlY3QoLTEpICU+JSBhcy5tYXRyaXgoKQpyb3cubmFtZXMobWF0KSA8LSB0aWIgJT4lIHB1bGwoMSkKCmNvbF9mdW4gPSBjaXJjbGl6ZTo6Y29sb3JSYW1wMihjKC01LCAwLCA1KSwgYygiYmx1ZSIsICJ3aGl0ZSIsICJyZWQiKSkKQ29tcGxleEhlYXRtYXA6OkhlYXRtYXAobWF0LCBjb2w9Y29sX2Z1bikKCmhyY3QkYGFtYWNyaW5lIChnbHljaW5lcmdpYylgIDwtIGMoNDcsODAsNTQsNDIsMTIsNTgsNzIsNDgpCmhyY3QkYGFtYWNyaW5lIChnYWJhL2dseWNpKWAgPC0gYygyMykKaHJjdCRgYW1hY3JpbmUgKGdhYmFuZXJnaWMpYDwtIHR1bmVkX2NhbGxzICU+JSBmaWx0ZXIoZ3JlcGwoImFtYWNyaW5lIixDVCkpICU+JSBwdWxsKGxlaWRlbjMpCmhyY3QkYGFtYWNyaW5lIChnYWJhbmVyZ2ljKWAgPC0gaHJjdCRgYW1hY3JpbmUgKGdhYmFuZXJnaWMpYFshaHJjdCRgYW1hY3JpbmUgKGdhYmFuZXJnaWMpYCAlaW4lIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjKGhyY3QkYGFtYWNyaW5lIChnbHljaW5lcmdpYylgLCBocmN0JGBhbWFjcmluZSAoZ2FiYS9nbHljaSlgKV0KCm1tX29icyRvYnMgJT4lIAogIGxlZnRfam9pbihocmN0ICU+JSBlbmZyYW1lKG5hbWUgPSAnQ1QnLCB2YWx1ZSA9ICdsZWlkZW4zJykgJT4lIHVubmVzdCgpLCBieSA9J2xlaWRlbjMnKSAlPiUgCiAgZmlsdGVyKGdyZXBsKCJhbWFjcmluZSIsIENUKSkgJT4lIAogIGdyb3VwX2J5KENUKSAlPiUgCiAgc2FtcGxlX24oNTAwMCwgcmVwbGFjZSA9IFRSVUUpICU+JSAKICB1bmlxdWUoKSAlPiUgCiAgZ2dwbG90KGFlcyh4PXVtYXAxLHk9dW1hcDIpKSArCiAgc2NhdHRlcm1vcmU6Omdlb21fc2NhdHRlcm1vcmUoYWVzKGNvbG9yID0gQ1QpLCBwb2ludHNpemUgPSAwLjgsIGFscGhhID0gMC41KSArCiAgZ2dyZXBlbDo6Z2VvbV9sYWJlbF9yZXBlbChkYXRhID0gLiAlPiUgZ3JvdXBfYnkoQ1QpICU+JSAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3VtbWFyaXNlKHVtYXAxID0gbWVkaWFuKHVtYXAxKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHVtYXAyID0gbWVkaWFuKHVtYXAyKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBhZXMobGFiZWwgPSBDVCwgY29sb3IgPSBDVCkpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYyhwYWxzOjphbHBoYWJldDIoKSwgcGFsczo6Z2xhc2JleSgpKSAlPiUgdW5uYW1lKCkpICsgCiAgY293cGxvdDo6dGhlbWVfY293cGxvdCgpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArCiAgZ2d0aXRsZSgiQW1hY3JpbmUiKQpgYGAKCiMjIyBIb3Jpem9udGFsCkRpZG4ndCBzZWUgdGhlIEgxL0gyIGRpc3RpbmN0aW9uIHdpdGggSVNMMSAvIExIWDIgCgpBbGwgYXJlIEgxPwpgYGB7ciwgZmlnLndpZHRoPTIwLCBmaWcuaGVpZ2h0PTR9CnRpYiA8LSBkaWZmX21tICU+JSAKICBsZWZ0X2pvaW4odHVuZWRfY2FsbHMsIGJ5ID0gYygiYmFzZSIgPSAnbGVpZGVuMycpKSAlPiUgIAogICNmaWx0ZXIoZ3JlcGwoImhvcml6b250IiwgQ1QpKSAlPiUgCiAgbXV0YXRlKEVOU0VNQkwgPSBnc3ViKCJcXC5cXGQrIiwiIixuYW1lcykpICU+JSAKICBsZWZ0X2pvaW4oY29udl90YWJsZSkgJT4lIAogIGZpbHRlcihTWU1CT0wgJWluJSBzdHJfdG9fdGl0bGUoYygnTEhYMScsJ0lTTDEnLCdPTkVDVVQxJywnT05FQ1VUMicpKSkgJT4lIAogIHNlbGVjdChTWU1CT0wsIGJhc2UsIGxvZ2ZvbGRjaGFuZ2VzKSAlPiUgCiAgcGl2b3Rfd2lkZXIodmFsdWVzX2Zyb20gPSBsb2dmb2xkY2hhbmdlcywgbmFtZXNfZnJvbSA9IGJhc2UpCgptYXQgPC0gdGliICU+JSBzZWxlY3QoLTEpICU+JSBhcy5tYXRyaXgoKQpyb3cubmFtZXMobWF0KSA8LSB0aWIgJT4lIHB1bGwoMSkKCmNvbF9mdW4gPSBjaXJjbGl6ZTo6Y29sb3JSYW1wMihjKC01LCAwLCA1KSwgYygiYmx1ZSIsICJ3aGl0ZSIsICJyZWQiKSkKQ29tcGxleEhlYXRtYXA6OkhlYXRtYXAobWF0LCBjb2w9Y29sX2Z1bikKYGBgCgojIyMgUmV0aW5hbCBHYW5nbGlvbgpDb3VsZG4ndCBtYWtlIGFueSBzZW5zZSBvZiB0aGVtIHVzaW5nIHRoZSBtYXJrZXJzIEkgY3VyYXRlZCBpbiAiSHVtYW5fTWF0dXJlX2V5ZV9mdWxsX19zdGFnZTRfbmV1cmFsLlJtZCIKYGBge3IsIGZpZy53aWR0aD02LCBmaWcuaGVpZ2h0PTR9CnRpYiA8LSBkaWZmX21tICU+JSAKICBsZWZ0X2pvaW4odHVuZWRfY2FsbHMsIGJ5ID0gYygiYmFzZSIgPSAnbGVpZGVuMycpKSAlPiUgIAogIGZpbHRlcihDVCA9PSAncmV0aW5hbCBnYW5nbGlvbicpICU+JSAKICBtdXRhdGUoRU5TRU1CTCA9IGdzdWIoIlxcLlxcZCsiLCIiLG5hbWVzKSkgJT4lIAogIGxlZnRfam9pbihjb252X3RhYmxlKSAlPiUgCiAgZmlsdGVyKFNZTUJPTCAlaW4lIHN0cl90b190aXRsZShjKCdUUEJHJywnVEJSMScsJ0ZBQlA0JywnQ0hSTkEyJywgJ0xNTzInLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnRU9NRVMnLCdTU1RSMicsJ0ZPWFAyJywnRk9YUDEnLCdQUlIzNScsJ0NBUlRQVCcsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdDREtOMkEnLCdBUlBQMjEnLCdPUE40JywgJ05FRk0nLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnVFVCQjMnKSkpICU+JSAKICBzZWxlY3QoU1lNQk9MLCBiYXNlLCBsb2dmb2xkY2hhbmdlcykgJT4lIAogIHBpdm90X3dpZGVyKHZhbHVlc19mcm9tID0gbG9nZm9sZGNoYW5nZXMsIG5hbWVzX2Zyb20gPSBiYXNlKQoKbWF0IDwtIHRpYiAlPiUgc2VsZWN0KC0xKSAlPiUgYXMubWF0cml4KCkKcm93Lm5hbWVzKG1hdCkgPC0gdGliICU+JSBwdWxsKDEpCgpjb2xfZnVuID0gY2lyY2xpemU6OmNvbG9yUmFtcDIoYygtNSwgMCwgNSksIGMoImJsdWUiLCAid2hpdGUiLCAicmVkIikpCkNvbXBsZXhIZWF0bWFwOjpIZWF0bWFwKG1hdCwgY29sPWNvbF9mdW4pCgoKYGBgCiMjIyBJbW11bmUKYGBge3J9CgoKdGliIDwtIGRpZmZfbW0gJT4lIAogIGxlZnRfam9pbih0dW5lZF9jYWxscywgYnkgPSBjKCJiYXNlIiA9ICdsZWlkZW4zJykpICU+JSAgCiAgZmlsdGVyKGdyZXBsKCJtYXN0fG1vbm98bmt8bWljcm9nbGlhIixDVCkpICU+JSAKICBtdXRhdGUoRU5TRU1CTCA9IGdzdWIoIlxcLlxcZCsiLCIiLG5hbWVzKSkgJT4lIAogIGxlZnRfam9pbihjb252X3RhYmxlKSAlPiUgCiAgZmlsdGVyKFNZTUJPTCAlaW4lIHN0cl90b190aXRsZShjKCJDRDY4IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlRNRU0xMTkiLCAiT2xmbWwzIiwgIyBtb25vY3l0ZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiRW1pbGluMiIsICJJTDFSTDEiLCAjIG1hc3QKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkNENCIsICN0Y2VsbAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQ0QxNjMiLCMgbWFjcm9waGFlZ2UKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkNEMTQiKSkpICU+JSAKICBtdXRhdGUoYmFzZSA9IGFzLmNoYXJhY3RlcihiYXNlKSkgJT4lIAogIG11dGF0ZShiYXNlID0gY2FzZV93aGVuKGdyZXBsKCJtYXN0fG1vbm98bmt8bWljcm9nbGlhIiwgQ1QpIH4gcGFzdGUwKGJhc2UsICcgLSAnLCBDVCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgVFJVRSB+IGJhc2UpKSAlPiUgCiAgc2VsZWN0KFNZTUJPTCwgYmFzZSwgbG9nZm9sZGNoYW5nZXMpICU+JSAKICBwaXZvdF93aWRlcih2YWx1ZXNfZnJvbSA9IGxvZ2ZvbGRjaGFuZ2VzLCBuYW1lc19mcm9tID0gYmFzZSkKCm1hdCA8LSB0aWIgJT4lIHNlbGVjdCgtMSkgJT4lIGFzLm1hdHJpeCgpCnJvdy5uYW1lcyhtYXQpIDwtIHRpYiAlPiUgcHVsbCgxKQoKY29sX2Z1biA9IGNpcmNsaXplOjpjb2xvclJhbXAyKGMoLTUsIDAsIDUpLCBjKCJibHVlIiwgIndoaXRlIiwgInJlZCIpKQpDb21wbGV4SGVhdG1hcDo6SGVhdG1hcChtYXQsIGNvbD1jb2xfZnVuKQoKaHJjdCRtYWNyb3BoYWdlIDwtIGMoNzQsIDgzKQoKYGBgCiMjIFR1bmVkIENUIENhbGxzCmBgYHtyLCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD04fQpocl90dW5lZF9jYWxscyA8LSB0dW5lZF9jYWxscyAlPiUgbGVmdF9qb2luKAogIGhyY3QgJT4lIAogICAgZW5mcmFtZShuYW1lID0gJ2hyQ1QnLCB2YWx1ZSA9ICdsZWlkZW4zJykgJT4lIAogICAgdW5uZXN0KCksCiAgYnkgPSAnbGVpZGVuMycKKSAlPiUgCiAgbXV0YXRlKGhyQ1QgPSBjYXNlX3doZW4oaXMubmEoaHJDVCkgfiBDVCwKICAgICAgICAgICAgICAgICAgICAgICAgICBUUlVFIH4gaHJDVCkpCgptbV9vYnMkb2JzICU+JSAKICBmaWx0ZXIoIWxlaWRlbjMgJWluJSByZW1vdmVfbGVpZGVuMykgJT4lIAogIGxlZnRfam9pbihocl90dW5lZF9jYWxscywgYnkgPSAnbGVpZGVuMycpICU+JSAKICBncm91cF9ieShDVCkgJT4lIAogIHNhbXBsZV9uKDUwMDAsIHJlcGxhY2UgPSBUUlVFKSAlPiUgCiAgdW5pcXVlKCkgJT4lIAogIGdncGxvdChhZXMoeD11bWFwMSx5PXVtYXAyKSkgKwogIHNjYXR0ZXJtb3JlOjpnZW9tX3NjYXR0ZXJtb3JlKGFlcyhjb2xvciA9IGhyQ1QpLCBwb2ludHNpemUgPSAxLjIsIGFscGhhID0gMC41KSArCiAgZ2dyZXBlbDo6Z2VvbV9sYWJlbF9yZXBlbChkYXRhID0gLiAlPiUgZ3JvdXBfYnkoQ1QsIGhyQ1QpICU+JSAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3VtbWFyaXNlKHVtYXAxID0gbWVkaWFuKHVtYXAxKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHVtYXAyID0gbWVkaWFuKHVtYXAyKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBhZXMobGFiZWwgPSBockNULCBjb2xvciA9IGhyQ1QpKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMocGFsczo6YWxwaGFiZXQyKCksIHBhbHM6OmdsYXNiZXkoKSkgJT4lIHVubmFtZSgpKSArIAogIGNvd3Bsb3Q6OnRoZW1lX2Nvd3Bsb3QoKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKwogIGdndGl0bGUoIlR1bmVkIENUIikKYGBgCgoKIyBTdGFnZSA0Ck91dHB1dCB0dW5lZCBjYWxscywgcmUtcnVuIHNjQU5WSSBtb2RlbGluZyB0byBmaW5hbGl6ZSBDVCBtb2RlbC4gTGlrZSB0aGUgaHVtYW4gbWF0dXJlIGV5ZSBtb2RlbHMsIHRoZSBjb3ZhcmlhdGVzIChyaWJvc29tZSwgbWl0bywgZXRjKSB3ZXJlIHJlbW92ZWQgZnJvbSB0aGUgbW9kZWxsaW5nIC0gdGhpcywgYW5lY2RvdGFsbHksIG1vZGVzdGx5IG1ha2UgdGhlIG1vZGVscyBhIGxpdHRsZSBiaXQgd29yc2UgYnV0IHdpbGwgc3Vic3RhbnRpYWxseSBtYWtlIGl0IGVhc2llciB0byBhcHBseSBuZXcgZGF0YSBhcyBJIGRvbid0IGhhdmUgdG8gZnVzcyBhYm91dCB3aXRoIHRyeWluZyB0byBhbHNvIGNyZWF0ZSB0aGUgc2FtZSBjb3ZhcmlhdGUgZmllbGRzLgoKIyMgT3V0cHV0IGJhcmNvZGVzCmBgYHtyfQpvdXRwdXQgPC0gbW1fb2JzJG9icyAlPiUgCiAgZmlsdGVyKCFsZWlkZW4zICVpbiUgcmVtb3ZlX2xlaWRlbjMpICU+JSAKICBsZWZ0X2pvaW4oaHJfdHVuZWRfY2FsbHMsIGJ5ID0gJ2xlaWRlbjMnKSAlPiUgCiAgIyBkcGx5cjo6cmVuYW1lKHR1bmVkQ1QgPSBDVCwKICAjICAgICAgICAgICAgICAgdHVuZWRfaHJDVCA9IGhyQ1QpICU+JSAKICBzZWxlY3QoLUNvdW50LCAtUmF0aW8pICU+JSAKICByZWxvY2F0ZShiYXJjb2RlLCBDZWxsVHlwZSwgTWFqb3JDZWxsVHlwZSwgYENUYCwgYGhyQ1RgKSAlPiUgCiAgZ3JvdXBfYnkoYWNyb3NzKGMoLXVtYXAxLCAtdW1hcDIpKSkgJT4lIHN1bW1hcmlzZSh1bWFwMSA9IG1lYW4odW1hcDEpLCB1bWFwMiA9IG1lYW4odW1hcDIpKSAKI291dHB1dCAlPiUgCiMgIHdyaXRlX2NzdihmaWxlID0gIn4vZ2l0L3NjRWlhRF9tb2RlbGluZy9kYXRhL21tMTExX2FkdWx0X2V5ZV90dW5lZF9DVF9jYWxscy4yMDI1MDEyMC5vYnMuY3N2Lmd6IikKCnNldC5zZWVkKDIwMjUwMTIwKQptbTExMV9mdWxsX2V5ZV9yZWZfYmNzMiA8LSBvdXRwdXQgJT4lIAogIGdyb3VwX2J5KHN0dWR5X2FjY2Vzc2lvbiwgaHJDVCkgJT4lIAogIHNhbXBsZV9uKDUwMCwgcmVwbGFjZSA9IFRSVUUpICU+JSAKICB1bmlxdWUoKQoKbW0xMTFfZnVsbF9leWVfcXVlcnlfYmNzMiA8LSBvdXRwdXQgJT4lCiAgZmlsdGVyKCFiYXJjb2RlICVpbiUgbW0xMTFfZnVsbF9leWVfcmVmX2JjczIkYmFyY29kZSkKCiNtbTExMV9mdWxsX2V5ZV9yZWZfYmNzMiRiYXJjb2RlICU+JSB3cml0ZShnemZpbGUoJ34vZ2l0L3NjRWlhRF9tb2RlbGluZy9kYXRhL21tMTExX21hdHVyZV9leWVfcmVmX2Jjcy5mdWxsLjIwMjUwMTIwLnN0YWdlNC5jc3YuZ3onKSkKCiNtbTExMV9mdWxsX2V5ZV9xdWVyeV9iY3MyJGJhcmNvZGUgJT4lIHdyaXRlKGd6ZmlsZSgnfi9naXQvc2NFaWFEX21vZGVsaW5nL2RhdGEvbW0xMTFfbWF0dXJlX2V5ZV9xdWVyeV9iY3MuZnVsbC4yMDI1MDEyMC5zdGFnZTQuY3N2Lmd6JykpCmBgYAoKCmBgYHtiYXNlIGJpb3d1bGYyIHM0LCBldmFsID0gRkFMU0V9CmNkIC9kYXRhL09HVkZCX0JHL3NjRWlhRC8yMDI0XzAyXzI4L3NuYWtlb3V0L21tMTExX21hdHVyZV9leWVfZnVsbC9zdGFnZTQKc291cmNlIC9kYXRhLyRVU0VSL2NvbmRhL2V0Yy9wcm9maWxlLmQvY29uZGEuc2ggJiYgc291cmNlIC9kYXRhLyRVU0VSL2NvbmRhL2V0Yy9wcm9maWxlLmQvbWFtYmEuc2gKbWFtYmEgYWN0aXZhdGUgcmFwaWRzX3NpbmdsZWNlbGwKcHl0aG9uIH4vZ2l0L3NjRWlhRF9tb2RlbGluZy93b3JrZmxvdy9zY3JpcHRzL2FwcGVuZF9vYnMucHkgLi4vLi4vLi4vbW0xMTEuYWRhdGEuc29sby4yMDI0MDgyNy5oNWFkIC9ob21lL21jZ2F1Z2hleWQvZ2l0L3NjRWlhRF9tb2RlbGluZy9kYXRhL21tMTExX2FkdWx0X2V5ZV90dW5lZF9DVF9jYWxscy4yMDI1MDEyMC5vYnMuY3N2Lmd6ICBtbTExMV9tYXR1cmVfZXllXzIwMjUwMTIwX2Z1bGxfMjAwMGh2Z18xMDBlXzIwbF9zdGFnZTQuaDVhZCAtLXRyYW5zZmVyX2NvbHVtbnMgQ1QsaHJDVAoKYGBgCgpub3RlIC0gYWRkaW5nIG1vcmUgZXBvY2hzICh3YXMgcHJldmlvdXNseSBoYXJkY29kZWQgdG8gNTApIHRvIHRoZSBzY2FudmkgbW9kZWxsaW5nIHN0ZXAgc2VlbXMgdG8gbWFrZSBhIG5vdGljZWFibGUgaW1wcm92ZW1lbnQgaW4gYWNjdXJhY3kuIEkgaGFkIHByZXZpb3VzbHkgc2V0IHRoaXMgdG8gNTAgYmVjYXVzZSBpdCBpcyBzdWNoIGEgc2xvdyBzdGVwIC0gZXNwZWNpYWxseSB3aGVuIHJ1bm5pbmcgaXQgYWNyb3NzIHRoZSAobXVjaCBsYXJnZXIpIGh1bWFuIGV5ZSBkYXRhc2V0LiAgCgpgYGB7cn0KbW1fb2JzNCA8LXB1bGxfb2JzKCd+L2RhdGEvc2NFaWFEX21vZGVsaW5nL21tMTExX21hdHVyZV9leWVfZnVsbC9tbTExMV9tYXR1cmVfZXllXzIwMjUwMTIwX3N0YWdlNF9ub0Nvdl8xNTBlcG9fMjAwMGh2Z181MGVfMjBsLm9icy5jc3YuZ3onLCBtYWNoaW5lX2xhYmVsID0gJ3NjQU5WSV9ockNUJywgbGFiZWwgPSAnaHJDVCcpCmBgYAojIyBUdW5pbmcKR3JvdXAgYnkgbGVpZGVuMywgY2VsbCB0eXBlIGFuZCByZW5hbWUgY2VsbCB0eXBlcyBpbiB0aGUgbWlub3JpdHkgKDUlIGluIHRoaXMgY2FzZSkgdG8gdGhlIG1ham9yaXR5IGNlbGwgdHlwZSBjYWxsCmBgYHtyLCBmaWcud2lkdGg9MTIsZmlnLmhlaWdodD0xMn0KIyByZXRhaW5zIENUIGNhbGxzID49IDAuMDUgb2YgYSBjbHVzdGVyLiBhbnl0aGluZyBiZWxvdyBnZXRzIGNoYW5nZWQgdG8gdGhlIGRvbWluYW50IGN0Cm1tX25vYnNfczRfY2xlYW5pbmcgPC0gbW1fb2JzNCRvYnMgJT4lIAogIGdyb3VwX2J5KGxlaWRlbjMsIHNjQU5WSV9ockNUKSAlPiUgCiAgc3VtbWFyaXNlKENvdW50ID0gbigpKSAlPiUKICBtdXRhdGUoUmF0aW8gPSBDb3VudCAvIHN1bShDb3VudCkpICU+JSAKICBtdXRhdGUoZG9taW5hbnRfY2VsbHR5cGUgPSBzY0FOVklfaHJDVFt3aGljaC5tYXgoQ291bnQpXSkgJT4lIAogIG11dGF0ZShDVGMgPSBjYXNlX3doZW4oUmF0aW8gPCAwLjA1IH4gZG9taW5hbnRfY2VsbHR5cGUsCiAgICAgICAgICAgICAgICAgICAgICAgICBUUlVFIH4gc2NBTlZJX2hyQ1QpKQoKbW1fbm9ic19zNCA8LSBtbV9vYnM0JG9icyAlPiUgCiAgbGVmdF9qb2luKG1tX25vYnNfczRfY2xlYW5pbmcgJT4lIAogICAgICAgICAgICAgIHNlbGVjdChsZWlkZW4zLCBzY0FOVklfaHJDVCwgQ1RjKSwgYnkgPSBjKCJsZWlkZW4zIiwic2NBTlZJX2hyQ1QiKSkKCiMgcXVpY2sgY29tcGFyZSBjb3VudCBkaWZmcwptbV9ub2JzX3M0ICU+JSBncm91cF9ieShockNULCBDVGMpICU+JSBtY0hlbHBlUnM6OnN1bV9yYXQoKSAlPiUgZGF0YS5mcmFtZQpgYGAKCiMjIFVNQVBzCmBgYHtyLCBmaWcud2lkdGg9OSwgZmlnLmhlaWdodD03fQptbV9ub2JzX3M0ICU+JSAKICBncm91cF9ieShsZWlkZW4zKSAlPiUgCiAgc2FtcGxlX24oMTAwMCwgcmVwbGFjZSA9IFRSVUUpICU+JSAKICB1bmlxdWUoKSAlPiUgCiAgZ2dwbG90KGFlcyh4PXVtYXAxLHk9dW1hcDIpKSArCiAgc2NhdHRlcm1vcmU6Omdlb21fc2NhdHRlcm1vcmUoYWVzKGNvbG9yID0gaHJDVCksIHBvaW50c2l6ZSA9IDAuOCwgYWxwaGEgPSAwLjkpICsKICBnZ3JlcGVsOjpnZW9tX2xhYmVsX3JlcGVsKGRhdGEgPSAuICU+JSBncm91cF9ieShockNUKSAlPiUgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN1bW1hcmlzZSh1bWFwMSA9IG1lZGlhbih1bWFwMSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB1bWFwMiA9IG1lZGlhbih1bWFwMikpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgYWVzKGxhYmVsID0gaHJDVCwgY29sb3IgPSBockNUKSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKHBhbHM6OmFscGhhYmV0MigpLCBwYWxzOjpnbGFzYmV5KCkpICU+JSB1bm5hbWUoKSkgKyAKICBjb3dwbG90Ojp0aGVtZV9jb3dwbG90KCkgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsKICBnZ3RpdGxlKCJTdGFnZSA0IFR1bmVkIENUIikKCm1tX25vYnNfczQgJT4lIAogIGdyb3VwX2J5KGxlaWRlbjMpICU+JSAKICBzYW1wbGVfbigxMDAwLCByZXBsYWNlID0gVFJVRSkgJT4lIAogIHVuaXF1ZSgpICU+JSAKICBnZ3Bsb3QoYWVzKHg9dW1hcDEseT11bWFwMikpICsKICBzY2F0dGVybW9yZTo6Z2VvbV9zY2F0dGVybW9yZShhZXMoY29sb3IgPSBDVGMpLCBwb2ludHNpemUgPSAwLjgsIGFscGhhID0gMC45KSArCiAgZ2dyZXBlbDo6Z2VvbV9sYWJlbF9yZXBlbChkYXRhID0gLiAlPiUgZ3JvdXBfYnkoQ1RjKSAlPiUgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN1bW1hcmlzZSh1bWFwMSA9IG1lZGlhbih1bWFwMSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB1bWFwMiA9IG1lZGlhbih1bWFwMikpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgYWVzKGxhYmVsID0gQ1RjLCBjb2xvciA9IENUYykpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYyhwYWxzOjphbHBoYWJldDIoKSwgcGFsczo6Z2xhc2JleSgpKSAlPiUgdW5uYW1lKCkpICsgCiAgY293cGxvdDo6dGhlbWVfY293cGxvdCgpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArCiAgZ2d0aXRsZSgiU3RhZ2UgNCBUdW5lZCBzY0FOVkkgQ1QiKQpgYGAKCiMjIFVNQVAsIHNwbGl0IGJ5IGNlbGwgdHlwZQpgYGB7ciwgZmlnLndpZHRoPTksIGZpZy5oZWlnaHQ9N30KbW1fb2JzNCRvYnMgJT4lIAogIGdyb3VwX2J5KGxlaWRlbjMpICU+JSAKICBzYW1wbGVfbigxMDAwLCByZXBsYWNlID0gVFJVRSkgJT4lIAogIHVuaXF1ZSgpICU+JSAKICBnZ3Bsb3QoYWVzKHg9dW1hcDEseT11bWFwMikpICsKICBzY2F0dGVybW9yZTo6Z2VvbV9zY2F0dGVybW9yZShhZXMoY29sb3IgPSBzY0FOVklfaHJDVCksIHBvaW50c2l6ZSA9IDIuOCwgYWxwaGEgPSAwLjkpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYyhwYWxzOjphbHBoYWJldDIoKSwgcGFsczo6Z2xhc2JleSgpKSAlPiUgdW5uYW1lKCkpICsgCiAgY293cGxvdDo6dGhlbWVfY293cGxvdCgpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArCiAgZ2d0aXRsZSgiU3RhZ2UgNCBzY0FOVkkgQ1QiKSArCiAgZmFjZXRfd3JhcCh+c2NBTlZJX2hyQ1QpCmBgYAoKYGBge3IsIGZpZy53aWR0aD05LCBmaWcuaGVpZ2h0PTd9Cm1tX25vYnNfczQgJT4lIAogIGdyb3VwX2J5KGxlaWRlbjMpICU+JSAKICBzYW1wbGVfbigxMDAwLCByZXBsYWNlID0gVFJVRSkgJT4lIAogIHVuaXF1ZSgpICU+JSAKICBnZ3Bsb3QoYWVzKHg9dW1hcDEseT11bWFwMikpICsKICBzY2F0dGVybW9yZTo6Z2VvbV9zY2F0dGVybW9yZShhZXMoY29sb3IgPSBDVGMpLCBwb2ludHNpemUgPSAyLjgsIGFscGhhID0gMC45KSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMocGFsczo6YWxwaGFiZXQyKCksIHBhbHM6OmdsYXNiZXkoKSkgJT4lIHVubmFtZSgpKSArIAogIGNvd3Bsb3Q6OnRoZW1lX2Nvd3Bsb3QoKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKwogIGdndGl0bGUoIlN0YWdlIDQgc2NBTlZJIENUIikgKwogIGZhY2V0X3dyYXAofkNUYykKYGBgCgojIyBDb25mdXNpb24gTWF0cml4CgpGcm9tIEF1dGhvciBMYWJlbCAoYWZ0ZXIgbXkgbWFudWFsIG5vbWVuY2xhdHVyZSBub3JtYWxpemF0aW9uKQpgYGB7ciwgZmlnLndpZHRoPTE2LCBmaWcuaGVpZ2h0PTE2fQptYWNoaW5lX2xhYmVsID0gJ3NjQU5WSV9ockNUJzsgbGFiZWwgPSAnTWFqb3JDZWxsVHlwZScKbW1fbm9ic19zNCAlPiUgCiAgZ3JvdXBfYnkoLmRhdGFbW2xhYmVsXV0sLmRhdGFbW21hY2hpbmVfbGFiZWxdXSkgJT4lIAogIHN1bW1hcmlzZShDb3VudCA9IG4oKSkgJT4lIAogIG11dGF0ZShSYXRpbyA9IENvdW50L3N1bShDb3VudCkpICU+JSAKICBnZ3Bsb3QoYWVzKHg9LmRhdGFbW2xhYmVsXV0seT0uZGF0YVtbbWFjaGluZV9sYWJlbF1dLGZpbGw9UmF0aW8sIGxhYmVsID0gcm91bmQoUmF0aW8sIDIpKSkgKyAKICBnZW9tX3RpbGUoKSArIAogIHNoYWRvd3RleHQ6Omdlb21fc2hhZG93dGV4dCgpICsgCiAgY293cGxvdDo6dGhlbWVfY293cGxvdCgpICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfYyhiZWdpbiA9IDAsIGVuZCA9IDEpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCB2anVzdCA9IDAuNSwgaGp1c3Q9MSkpIApgYGAKCmBgYHtyLCBmaWcud2lkdGg9MTYsIGZpZy5oZWlnaHQ9MTZ9Cm1hY2hpbmVfbGFiZWwgPSAnQ1RjJzsgbGFiZWwgPSAnaHJDVCcKbW1fbm9ic19zNCAlPiUgCiAgZ3JvdXBfYnkoLmRhdGFbW2xhYmVsXV0sLmRhdGFbW21hY2hpbmVfbGFiZWxdXSkgJT4lIAogIHN1bW1hcmlzZShDb3VudCA9IG4oKSkgJT4lIAogIG11dGF0ZShSYXRpbyA9IENvdW50L3N1bShDb3VudCkpICU+JSAKICBnZ3Bsb3QoYWVzKHg9LmRhdGFbW2xhYmVsXV0seT0uZGF0YVtbbWFjaGluZV9sYWJlbF1dLGZpbGw9UmF0aW8sIGxhYmVsID0gcm91bmQoUmF0aW8sIDIpKSkgKyAKICBnZW9tX3RpbGUoKSArIAogIHNoYWRvd3RleHQ6Omdlb21fc2hhZG93dGV4dCgpICsgCiAgY293cGxvdDo6dGhlbWVfY293cGxvdCgpICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfYyhiZWdpbiA9IDAsIGVuZCA9IDEpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCB2anVzdCA9IDAuNSwgaGp1c3Q9MSkpIApgYGAKCmBgYHtyfQojbW1fbm9ic19zNCAlPiUgd3JpdGVfY3N2KCJ+L2RhdGEvc2NFaWFEX21vZGVsaW5nL21tMTExX21hdHVyZV9leWVfZnVsbC9tbTExMV9tYXR1cmVfZXllXzIwMjUwMTIwX3N0YWdlNF9ub0Nvdl8xNTBlcG9fMjAwMGh2Z181MGVfMjBsLnR1bmVkU3RhZ2U0LnYwMS5vYnMuY3N2Lmd6IikKYGBgCgpgYGB7cn0Kc2Vzc2lvbkluZm8oKQpgYGA=